Adding Unit Tests to an existing iOS project with Xcode 4

When you build a new iPhone or iPad app from scratch, you can generate a new project with tests using Xcode’s project templates, and the test dependency is set up correctly.

However, if you add a unit test target to an existing iOS project, there are some manual steps required to set up the target correctly. Following these steps will get you up and running quickly:

  1. Project MyExistingApp -> + Add Target -> iOS -> Other -> Cocoa Unit Testing Bundle
  2. Name the new target something like “Unit Tests” or “MyAppTests”
  3. Select your new “Unit Tests” target and click the Build Phases tab, expand Target Dependencies and add MyExistingApp as as a Target Dependency
  4. Click the Build Settings tab and set the Bundle Loader setting to
    $(BUILT_PRODUCTS_DIR)/MyExistingApp.app/MyExistingApp
  5. Set the Test Host build setting to
    $(BUNDLE_LOADER)
  6. Go back to your app target (not the test target), set the Symbols Hidden by Default build setting to
    NO

That should take care of it, and you can now start adding tests for your app. With this configuration, you continue to add application classes only to your app target, and add test classes only to your test target. Since the tests run in the app running in the simulator, the code under test can safely instantiate views and fonts, which would otherwise cause OCunit to crash when running without the Test Host setting.

Undefined symbols for architecture i386

If you add a unit test target without following these steps, you will likely run into linker errors when you try to run tests, like the following:

Undefined symbols for architecture i386:
  "_OBJC_CLASS_$_SomeClassUnderTest", referenced from:
      objc-class-ref in SomeTest.o

This is because your tests reference classes in your app which aren’t present in the test binary. The target dependency just causes the app to build before building the tests. Setting the Bundle Loader, Test Host, and Symbols Hidden By Default settings as described above allows the tests to link against your app, and should resolve these errors.

If you continue to see linking errors check this stack overflow post for other possible solutions (thanks Johan).

51 thoughts on “Adding Unit Tests to an existing iOS project with Xcode 4

  1. Thanks a lot for this post.

    One question, in my case the MyExistingApp is executed on the simulator, can the configuration be changed in order to avoid the execution of the app?

    • If you skip step 5 you can run your tests outside of the simulator. Here at Two Bit Labs we typically don’t do that though since you can really only effectively test the model layer outside of the simulator. By running your tests in the simulator you’ll be able to load up controllers and views, click buttons, and do a whole bunch of interesting integration and functional testing that you can’t effectively do outside of the simulator.

  2. Thanks! Unfortunately I’m still getting the i386 error, even after setting the Symbols Hidden by Default to no in the App target. Anything I’m not seeing maybe?

  3. Make sure you have step 4 (setting the value of the Bundle Loader) correct. It’s an easy one to get wrong. For example if your app includes a space like “My App” then you’d need to set the value to:

    $(BUILT_PRODUCTS_DIR)/My App.app/My App

    • I did the same thing, and still getting the same problem. Is there another way around ? I did all the steps all over again, and its still giving the:
      Undefined symbols for architecture i386:

    • What should I put as the Bundle Loader path for a static library? I tried “$(BUILT_PRODUCTS_DIR)/commonios.a/commonios” and a few others, but none of them worked.

  4. I have checked it, if I put in a different name I get different related errors, so I’m sure it’s correct :-) Still can’t seem to work out the problem, I’ll keep trying. Thanks though

  5. I tried this with an Mac Application project. The only thing I had to change there was to set the Bundle Loader to:

    $(BUILT_PRODUCTS_DIR)/MyExistingApp.app/Contents/MacOs/MyExistingApp

    instead of

    $(BUILT_PRODUCTS_DIR)/MyExistingApp.app/MyExistingApp

    Thus adding the subfolders ‘Contents/MacOs’

  6. I have build a new xcode project with unit test, but ran in the above described issue:
    Undefined symbols for architecture i386:
    “_OBJC_CLASS_$_SomeClassUnderTest”, referenced from:
    objc-class-ref in SomeTest.o

    I tried your settings but couldn’t find “Symbols Hidden by Default “. I’m using Xcode 4.2. Did the this settings name changes?

  7. This is very helpful. However I am getting a link error at run time (not the i386 error at compile time)

    The test bundle at /Users/me/dev/build/Debug-iphonesimulator/BackupAndRestoreTests.octest could not be loaded because a link error occurred. It is likely that dyld cannot locate a framework framework or library that the the test bundle was linked against

    Have you any idea how I have configured things incorrectly?

  8. Hi, I followed your instructions and was able to compile succesfully. However, when running the test target i get a runtime error:

    warning: This configuration supports “Darwin64″ but is attempting to load
    an executable of type i386 which is unlikely to work.
    Attempting to continue.
    `/usr/lib/dyld’ has changed; re-reading symbols.
    warning: Inconsistent DBX_SYMBOL_SIZE

    /Users/…/Tests.app/Tests: /Users/…Tests.app/Tests: cannot execute binary file.

    This happens even if there havent been anything added to the Tests app that targets the Existing App. Another error I was getting was
    ld: -bundle_loader can only be used with -bundle.
    That went away when I changed Mach-O Type to bundle….

    Thanks anyways for the post, im almost there!

    • When I set the Mach-O build setting on my Spec target, I discovered that the tests are not displayed on the iPhone as they had been doing previously. Rather, the application launches and then immediately closes. Leaving the Mach-O build setting to executable gives me the error you had:

      ld: -bundle_loader can only be used with -bundle.

  9. In Xcode 4.2, I needed to go to Product – Edit Scheme, click on Test and add the MyAppTests bundle to Tests. Once I did this, the Test menu item was enabled and I could run tests in the simulator and on a device.

  10. Many thanks for your great post. Your solution fixed some of my errors however when I run the tests from the command line (I’m using XCode 4.2 & iOS5), I get:

    Run unit tests for architecture ‘i386′ (GC OFF)
    /Developer/Tools/RunPlatformUnitTests.include:419: note: Running tests for architecture ‘i386′ (GC OFF)
    2011-12-08 11:39:27.666 otest[2017:7803] The test bundle at /Users/Mandy/Documents/TheElementsTwo/build/Debug-iphonesimulator/AnotherTryUnitTest.octest could not be loaded because a link error occurred. It is likely that dyld cannot locate a framework framework or library that the the test bundle was linked against, possibly because the framework or library had an incorrect install path at link time.
    /Developer/Tools/RunPlatformUnitTests.include:448: error: Failed tests for architecture ‘i386′ (GC OFF)

    /Developer/Tools/RunPlatformUnitTests.include:462: note: Completed tests for architectures ‘i386′

    ** BUILD FAILED **

    The following build commands failed:
    PhaseScriptExecution “Run Script” build/TheElements.build/Debug-iphonesimulator/AnotherTryUnitTest.build/Script-38382AC61490D6FE00DE43EA.sh

    Many thanks for any help you can give!

  11. Great post, exactly the information that I was looking for. The one thing that got me at first was setting the “Hide symbols by default”. I kept setting it for the testing target, once I realized it should be set for the existing app target, I was golden.

    Thanks again.

    • If you don’t do that, you’ll get a bunch of “Undefined symbol” errors when you try to build your test target, because it can’t see the symbols from your app target. You can leave symbols hidden in your Release configuration.

  12. Thanks for this. It really helped.

    Just in case other people have the same bad eyes I had the 1st time, the “Symbols Hidden by Default” parameter is to set to “No” on the target you TEST, not the unit tests target. Lost 2 hours… ;)

  13. Got the error “ld: -bundle_loader can only be used with -bundle” after following your tips. Can’t find anything about this error. How do I solve it?

  14. Thank you very much for this tutorial.

    I followed the steps but it seemed anyhow that my test is no run.
    I finished up by adding manual the my app (.m)files in to compile sources in order to get my test run.
    Is there any other way to solve this problem?

    • Go back to your app target (not the test target), set the Symbols Hidden by Default build setting to YES. Compile it will fail.
      then go back again to our app target (not the test target), set the Symbols Hidden by Default build setting to NO and compile again. I

  15. I am getting the following error, but the file does exist on the path specified:

    The executable for the test bundle at /Users/me/Library/Developer/Xcode/DerivedData/OldApp-cuzlyiywswfchbgyclwrcjklrtoa/Build/Products/Debug-iphonesimulator/OldAppTests.octest

    Any ideas on why this might be happening?

    Thanks.

  16. So I deleted my Unit Test target that Xcode created for me since it was getting a dyld link error. Followed your direction exactly, and everything is working perfect. So, thank you for that.

    But the previous unit test target, when there was a failure, I could click on the failure, and it would show me the source code line where the fail occurred. Now this new target doesn’t.

    Any suggestions on returning the code line functionality.

    Thank you again for a great tutorial.

  17. I ran into an issue in that I import my key frameworks (that I need throughout the app, for example cocos2d.h) in my .pch file on my main target. I was getting ‘file not found’ errors when trying to build my unit tests that have the main target as a dependency. Ensuring these imports from the main target’s .pch were also in the test target’s .pch solved the problem.

  18. Don’t forget you’ll probably want to delete the scheme for the testing and then edit the project scheme so you can include the test bundle to the Test configuration. Then you just need to hit cmd-U to run your tests without changing schemes.

  19. Pingback: Cocos2D Revised Template « Thought Repository

  20. Pingback: Recipe: a Podfile with different Pods per target | Thought Repository

  21. Great post, it fixed my exact problem. I wonder why Xcode just doesn’t do this for you though during the unit test project setup? In my case I set up the unit test project after I setup the project under test first. IOW, I did not choose to add a unit test target during the initial project setup. Would that have made a difference?

  22. The last step is to configure your unit tests to run when you trigger a test (⌘U). Click on your scheme name and select “Edit Scheme…”. Click on “Test” in the sidebar followed by the “+” in the bottom left corner. Select your testing target and click “OK”.

  23. Hi All,
    if you have done all the above and it still isn’t working check out this post (from the SO link provided in the article)

    http://stackoverflow.com/a/12600218

    Basically it says
    Set Deployment Postprocessing (in the Deployment section of Build Settings) to NO for the Debug target.

    Before I did this, the executable was being stripped, and the link would fail with

    Undefined symbols for architecture i386:
    “_OBJC_CLASS_$_SomeClassUnderTest”, referenced from:
    objc-class-ref in SomeTest.o
    No matter that Strip Linked Product and Strip Debug Symbols During Copy were set to NO, it made no difference – only changing the Deployment Postprocessing setting finally made sure that the symbols were not stripped.

    Cheers
    Kle

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>