151

I am trying to write some iOS logic tests against classes in my project that use functionality from some of the libraries in my podspec. I am using the standard unit test bundle provided in Xcode (although not Application Tests, just Unit Tests).

For example, I use Magical Record, and I have that library linked in my podspec. It is present in the Pods project in my workspace, and works as expected when the app is running in the simulator or on the device. When I try to link to the test the object that uses Magical Record, however, I get a linker error stating that it can't find the selectors from Magical Record. I have tried updating my HEADER_SEARCH_PATH in my logic testing bundle, even hard coding it to the headers directory created by CocoaPods, but no luck.

I can run unit tests against classes that do not use CocoaPods libraries with no problem.

Am I going about this wrong? Should I be doing something else to get the compiler to see the CocoaPods libraries?

Mark Struzinski
  • 32,945
  • 35
  • 107
  • 137

14 Answers14

225

CocoaPods 1.0 has changed the syntax for this. It now looks like this:

def shared_pods
    pod 'SSKeychain', '~> 0.1.4'
    ...
end

target 'Sail' do
    shared_pods
end

target 'Sail-iOS' do
    shared_pods
end

Pre CocoaPods 1.0 answer

What you want to use is link_with from your Podfile. Something like:

link_with 'MainTarget', 'MainTargetTests'

Then run pod install again.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Keith Smiley
  • 61,481
  • 12
  • 97
  • 110
  • 7
    This immediately fixed up the problem for me. – mttrb Aug 02 '13 at 09:45
  • 9
    I get strange errors with this - when testing, `isSubclassOfClass:` calls return `NO` where they should return `YES`. The only reason I can explain this is that the dependencies really get linked to both the main and the test target, and when the test target's bundle loader loads the main bundle, it cannot decide which class to take. – fabb Sep 26 '13 at 06:07
  • 4
    I have the same issue with `isKindOfClass:` returning `NO` when it should return `YES`. If I log the pointer to the `Class` of my object I'm testing and the `Class` of the class I want to compare against they are two different values. Clearly my code from the app bundle is using a different symbol for the class than the code from my unit tests. Has anyone found a way to resolve this? – Nicholas Hart Oct 15 '13 at 22:35
  • Actually, I found a solution for my problem. I wouldn't advocate this for production code because it seems kinda hacky, but for unit test code it does the trick. Instead of creating an object via `[[MyClass alloc] init]` I created a category on `MyClass` which returns the class from the main bundle, like so: `- (Class)main_class { return [NSBundle mainBundle] classNamed:NSStringFromClass([MyClass class])]; }` and then alloc/init an instance in my unit test like so: `[[[MyClass test_class] alloc] init]`. – Nicholas Hart Oct 15 '13 at 22:54
  • 2
    I don't think this is a good way to go due to the errors some others have mentioned. Stick with updating the configuration file 'based on' bit. Make sure you haven't linked libPods.a twice. – Bob Spryn May 13 '14 at 20:58
  • 1
    If you go this route, make sure to remove the libPods.a file from the linked libraries in the tests target. You don't want it linked twice, as the comments above have pointed out. – Bob Spryn May 13 '14 at 21:03
  • that's the way to go! Thanks, upvoted for that, can't downvote the more popular but imho WRONG solution. – mnl May 31 '14 at 22:16
  • This seems to be the recommended solution based on the cocoapods site. "If you want multiple targets, like adding tests, to share the same pods...link_with" and is the only way I could get it to work on xcode 5. – uberdog Jun 07 '14 at 01:41
  • this is the way to go. Super clean solution. – mikebz Jun 14 '14 at 13:41
  • Gabox: if you're having issues with this you should submit a ticket on the main CocoaPods repo on GitHub – Keith Smiley Jun 14 '14 at 19:50
  • 1
    In XCode 5.1 Make sure you remove libPods.a from "Link Binary with Libraries" or else you'll get strange behavior with isKindOfClass. – dustin.schultz Jun 27 '14 at 23:28
  • 3
    This should be the accepted answer since this is the official CocoaPods way to setup Pods with multiple targets. Thx a lot Keith! – cschuff Aug 20 '14 at 16:22
  • 1
    @NicholasHart and @fabb: You have probably found a way to handle the problem already that worked for you, but I found a good solution (IMHO) to the problem with `isSubclassOfClass:` returning `NO`. See my answer here: http://stackoverflow.com/questions/14512792/libraries-not-found-when-using-cocoapods-with-ios-logic-tests/27165120#27165120 – JRV Nov 27 '14 at 07:26
  • I did three things to silence all my errors like this one. 1. add the offending class to the Test project's membership (select class in Navigator on left and then look at the File Inspector on right, toggle checkmark in Membership). 2. Make the class and the func public. 3. Be sure to add the "link_with" line after the list of pods to install. – Peter Brockmann May 06 '15 at 15:00
  • TL;DR The first solution (CocoaPods 1.0) works for pre 1.0 CocoaPods as well. – Michael Kessler Jun 02 '16 at 06:51
  • 1
    this is wrong because this duplicates the symbols between the test target and the main target. That will work for regular targets, but not for the test targets. Test targets run and then side-load the main app, so you get the duplicate symbols and the errors people have been mentioning here where isKindOfClass and such don't behave as expected. – Kamchatka Oct 25 '16 at 09:38
  • i'm still getting no such a module on widget target, anybody have this problem? – Okan Kocyigit Jan 08 '17 at 13:17
  • This is not the official way to do things in CocoaPods 1.x; you'll get duplicate symbols. See http://stackoverflow.com/a/40866889/2799670 – Darren Black Mar 17 '17 at 11:37
174

I figured this one out by looking at how the main target of my app was receiving settings from the CocoaPods library. CocoaPods includes an .xcconfig file named Pods.xcconfig. This file contains all of the header search paths.

If you look at your project in the project navigator and click the Info tab, you will see your build configurations listed on the top section. If you open the disclosure triangle for your different configurations, you will see Pods listed under your main target. I had to click the drop down and add Pods to the logic test target as well.

Configurations Snapshot

I also had to copy the settings of $(inherited) and ${PODS_HEADERS_SEARCH_PATHS} from my main target and copy them over to the logic test target under Build Settings/HEADER_SEARCH_PATHS.

Finally, I had to add libPods.a in the Link Binary with Libraries build phase for my logic tests target.

Hope this is able to help someone else.

Mark Struzinski
  • 32,945
  • 35
  • 107
  • 137
  • Brilliant! I use MagicalRecord and also OCMockito and OCHamcrest for unit testing. With this fix I can now install them all through CocoaPods! Thanks! – Fogmeister Mar 22 '13 at 22:27
  • 4
    This worked for me, thanks. NOTE.. I didn't need to add the libPods.a into both the test proj and main proj. This causes a duplicate symbol error – Craig Bruce Jun 04 '13 at 01:36
  • For me, I also had to copy the "User-Defined" build settings. The Header Search Paths refer to $PODS_ROOT which was not defined on the test target. You can add it by going to Editor->Add Build Setting->Add User-Defined Setting then copying the $PODS_ROOT value from the main target. – Shinigami Jan 02 '14 at 20:18
  • 11
    This is not the correct way to fix this. See answer with link_with. You can also specify different pods on a per [target](http://guides.cocoapods.org/syntax/podfile.html#target) basis in your pod file, i.e., only include OCMockito in your test target. – dbainbridge Mar 15 '14 at 23:21
  • Yes, yes, yes! Before this answer I had to delete Test target from my projects! Thanks man :) – Josip B. Nov 18 '14 at 09:34
  • If your unit test is in Swift, make sure your test target has a bridging header, and that it is set in your build settings under "Objective-C Bridging Header". – Mike Taverne Jan 07 '15 at 19:44
  • "Disclosure triangle." Fancy! – jxmorris12 May 19 '16 at 15:54
53

There is a solution I found here Unit Tests With CocoaPods:

Open the project file in Xcode, then choose the Project (not the target), in the right panel, there is a section called Configurations. Choose Pods in the "Based on Configuration file" column for your test target.

enter image description here

Mingming
  • 2,189
  • 20
  • 21
  • Well, what if there are test-specific dependencies, like `Specta` that you want to link with the test project but not with the main project? :S – fatuhoku Jan 17 '15 at 15:48
  • This worked and doesn't require any changes to the pod config or setup... Excellent solution. – Richard Nov 02 '15 at 17:14
  • 1
    Although this solution may create an error: `Class Foo is implemented in both MyApp and MyAppTestCase. One of the two will be used. Which one is undefined.` This seems to be caused by a bug in Cocoapods; see @JRV answer below. – Richard Nov 02 '15 at 23:11
  • Those are not just warnings. With such a setup no proper Xcode code coverage data is generated and unit tests just hang during launch in most cases. – i4niac Mar 22 '16 at 09:10
  • I have imported the Estimote SDK manually by drag and drop, I am not getting pods. How to resolve this? – Guru Teja Dec 29 '16 at 08:00
19

I agree with the other answers telling that it is necessary to get the libraries linked to the test targets. However none of the suggestions so far helped me. As @fabb writes in a comment: "when testing, isSubclassOfClass: calls return NO where they should return YES. The only reason I can explain this is that the dependencies really get linked to both the main and the test target, and when the test target's bundle loader loads the main bundle, it cannot decide which class to take." I get the same problem with all the previous suggestions in this thread.

The solution that I got to work was to update my Podfile to define specific Pods for my main target and my test target:

target 'MyTarget' do
   pod 'AFNetworking', '~> 2.5.0'
   pod 'Mantle', '~> 1.5'
end

target 'MyTargetTests' do
   pod 'OCMockito', '~> 1.3.1'
end

It was necessary to specify a Pod for my test target even though I did not use any test specific Pods. Otherwise CocoaPods would not insert the necessary linking logic in my project.

This link is what helped me come to this conclusion.

JRV
  • 1,702
  • 1
  • 17
  • 27
6

I added :exclusive => true to avoid duplicated symbol errors in the application test target.

target 'myProjectTests', :exclusive => true do
   pod 'OCMock', :head
   pod 'XCTAsyncTestCase', :git => 'https://github.com/iheartradio/xctest-additions.git'
end

link_with 'myProject', 'myProjectTests'

When I changed the application test target to the logic unit test one, the linker error occurs. After I remove :exclusive => true, everything works again.

target 'myProjectTests', do
   pod 'OCMock', :head
   pod 'XCTAsyncTestCase', :git => 'https://github.com/iheartradio/xctest-additions.git'
end

link_with 'myProject', 'myProjectTests'

:exclusive => true states that everything outside do...end should NOT be linked to myProjectTests, which is reasonable in application test targets, but it will cause linker errors in logic test targets.

Hai Feng Kao
  • 5,219
  • 2
  • 27
  • 38
  • Exclusive was the solution for me, as shown in [kylef's answer on this CocoaPods issue](https://github.com/CocoaPods/CocoaPods/issues/1411) , which was found thanks to JRV's answer on this question! – karlbecker_com Jan 06 '15 at 19:02
  • 1
    Yes, everyone should read that issue on github linked by @karlbecker_com. Seems this is just a long running limitation of cocoapods. According to the discussion there, link_with is not necessary. Simply add the test target and use :exclusive. If your test target doesn't need any specific pods, add one anyway otherwise cocoapods won't process it. – kball Nov 14 '15 at 23:42
  • @kball Which one doesn't need link_with? The application test or the logic unit test? – Hai Feng Kao Nov 17 '15 at 05:13
  • Unless you have another reason to be using it, you shouldn't need link_with at all. And generally speaking you don't want to link those pods with your test bundle. They should only be linked once, in the app bundle, and then referenced by your tests through the dependency (ensuring Symbols Hidden by Default is off). Otherwise the behavior is undefined because two versions of the pods will exist - one included in the app target, one in the test target. – kball Nov 18 '15 at 02:17
6

You can use link_with according to @Keith Smiley solution.

In case you have common pods, and specifics for each target, you might want to use the "def" option to define group of pods. and use the "def" later in exclusive target.

def import_pods
    pod 'SSKeychain'
end

target 'MyProjectTests', :exclusive => true do
  import_pods
end

target 'MyProject', :exclusive => true do
  import_pods
  pod 'Typhoon'
end

in the example above, I added 'SSKeychain' to the both targets, and 'Typhoon' only to 'MyProject' target

Elihay
  • 123
  • 1
  • 5
5

My solution to this problem was to change my Podfile to include the library in both targets like this

target "MyApp" do  
    pod 'GRMustache', '~> 7.0.2'
end

target "MyAppTests" do
    pod 'GRMustache', '~> 7.0.2'
end

And since I'm using swift I also had to configure the test target to include the MyApp-Bridging-Header.h file. (In the Swift Compiler group under the Build Settings tab)

Qw4z1
  • 3,041
  • 1
  • 24
  • 36
  • 3
    Careful — this will increase your build times by lots, as you keep adding more pods! – fatuhoku Jan 03 '15 at 12:30
  • @fatuhoku didn't know that. Can you provide some insight as to why it increases build time? – Qw4z1 Jan 03 '15 at 22:39
  • 2
    Well each mention of a pod is a target in your `Pods` project. By mentioning your pods twice (once for tests and once for the app), you'll have two sets of targets. This effectively doubles the configuration work `pod install` has to do. This won't be an issue until you have > 15 pods though so don't worry too much until then. – fatuhoku Jan 06 '15 at 11:53
  • 1
    This is the only solution that works for me with Cocoapods 1.0 – William Entriken Jan 21 '16 at 19:52
  • As of 1.x, this is the official method for tests inheriting app dependencies: http://stackoverflow.com/a/40866889/2799670 – Darren Black Dec 06 '16 at 10:41
4

I had a similar occurrence when I lost some library files during some version control. I still saw the library file in my Pods but with the actual code missing, XCode said it was gone. To my dismay, running 'pod install' wasn't immediately bringing the lost files back.

I had to remove and replace the pod manually by doing the following:

  1. Remove the library from the Podfile
  2. Run 'pod install' to remove the library completely
  3. Put the library back into the Podfile
  4. Run 'pod install' again

This should put the library in question back in it's original form.

Maxwell
  • 6,532
  • 4
  • 37
  • 55
2

It's also worth noting that if you have libPods.a added twice, you'll get some nasty error like this:

232 duplicate symbols for architecture i386

To fix it, just delete one of the libPods.a references in your Project Explorer.

Mat Ryer
  • 3,797
  • 4
  • 26
  • 24
2

As of CocoaPods 1.x, there's a new way to declare shared dependencies between a target and the corresponding test target. I'd been using the accepted solution by Mark Struzinski to this point, but using this method yielded a massive number of warnings when running my tests that:

Class SomeClass is implemented in both /Path/To/Test/Target and /Path/To/App/Target. One of the two will be used. Which one is undefined.

With CocoaPods 1.x we can declare our -Test target as inheriting via the parent target's search paths, like so:

target 'MyApp' do
    pod 'aPod'
    pod 'anotherPod'
    project 'MyApp.xcodeproj'
end
target 'MyAppTests' do
    inherit! :search_paths
    project 'MyApp.xcodeproj'
end

This will result in the -Test target having access to the dependencies of the app target, without multiple binary copies. This has seriously sped up test build times for me.

Darren Black
  • 1,030
  • 1
  • 9
  • 28
2

Try This it's working for me ,

We need to set Pods in Configurations ,

The Project->Info->Configurations in the Xcode project (your project) should be set to main project 'Pods' for Debug, Release (and what else you have). See "Headers not found – search paths not included"

enter image description here

Hope this is help to some one .

Jaywant Khedkar
  • 5,941
  • 2
  • 44
  • 55
1

I am working with GoogleMaps Objective-C POD integration on iOS with my Swift app and so for me the issue was that the Test target didn't have a reference to the Bridge Header File (SWIFT_OBJC_BRIDGING_HEADER) in the Build Settings. Make sure both your app and test app targets point to that so that the 3rd party API calls (maps API, etc.,) can be used in swift unit tests.

appledevguru
  • 105
  • 9
  • 1
    I have a similar setup as you. I have already added the bridging header to the test target, However I get the error "No such module 'GoogleMaps' " on `import GoogleMaps`. – Nicolas Miari Feb 17 '16 at 07:32
0

Next syntax gives best result for me (tested under cocoapod v.1.2.1):

https://github.com/CocoaPods/CocoaPods/issues/4626#issuecomment-210402349

 target 'App' do
    pod 'GoogleAnalytics' , '~> 3.0'
    pod 'GoogleTagManager' , '~> 3.0'

     pod 'SDWebImage', '~>3.7'
     platform :ios, '8.0'
     use_frameworks!

     target 'App Unit Tests' do
         inherit! :search_paths
     end
 end

Without this I have warnings while test run about duplicate symbols.

After this warnings were disappear.

Maxim Kholyavkin
  • 4,463
  • 2
  • 37
  • 82
0

I had issues using OpenCV under XCTest. It was giving me linker errors of Undefined symbols for architecture arm64 for classes like cv::Mat. I am installing OpenCV through CocoaPods using pod 'OpenCV', '~> 2.0' under the main target. No matter how hard I tried to put the OpenCV dependency under the test target or use inherit! :search_paths none of it worked. The solution was to create an abstract_target like so:

# Uncomment the next line to define a global platform for your project
platform :ios, '6.1.6'

abstract_target 'Shows' do
  pod 'RMVision', path: '../..'
  pod 'RMShared', path: '../../../RMShared'
  pod 'OpenCV', '~> 2.0'

  target 'RMVisionSample' do
    # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
    # use_frameworks!

    # Pods for RMVisionSample
  end

  target 'RMVisionSampleTests' do
    # inherit! :search_paths
    # Pods for testing
  end

  target 'RMVisionBenchmarks' do
    # inherit! :search_paths
    # Pods for testing
  end

end 

Also useful are the pod deintegrate & pod clean commands which help to cleanup the project and make sure that you start fresh when testing. You can install those two using [sudo] gem install cocoapods-deintegrate cocoapods-clean.

Foti Dim
  • 1,303
  • 13
  • 19