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).

54 Comments

  1. Claudio
    July 28, 2011

    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?

    Reply
    • Todd Huss
      July 29, 2011

      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.

      Reply
  2. chirred
    August 23, 2011

    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?

    Reply
  3. Todd Huss
    August 23, 2011

    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

    Reply
    • Edward
      November 30, 2011

      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:

      Reply
    • Kyle
      March 12, 2012

      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.

      Reply
  4. chirred
    August 24, 2011

    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

    Reply
    • Sam
      September 11, 2011

      In your tests target try setting “Test After Build” to Yes.

      Reply
  5. Beat
    September 1, 2011

    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’

    Reply
  6. PokerChang
    September 6, 2011

    Great post to get started on OCUnit. However, I ran into some strange bugs while testing UINavigationController and UITableView. Any help is appreciated!

    http://stackoverflow.com/questions/7326961/unit-test-uinavigationcontroller-and-uitableview

    Reply
  7. Rémi
    October 18, 2011

    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?

    Reply
    • Christopher Pickslay
      October 18, 2011

      It’s still the same. It may show up as “GCC_SYMBOLS_PRIVATE_EXTERN” if you have “Show settings names” set from the Editor menu.

      Reply
  8. Paulo
    November 4, 2011

    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?

    Reply
  9. David
    November 20, 2011

    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!

    Reply
    • Daniel Dyba
      February 8, 2012

      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.

      Reply
  10. scandidavian
    November 30, 2011

    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.

    Reply
    • David
      September 7, 2012

      This needs to be added to the instructions! Thanks.

      Reply
  11. MandyW
    December 8, 2011

    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!

    Reply
  12. Jacob
    December 16, 2011

    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.

    Reply
    • Christopher Pickslay
      December 16, 2011

      Cool, glad to hear it helped!

      Reply
    • Jason Ryan
      February 3, 2015

      Thank you so much, I want to cry now! So exciting.

      Reply
  13. jianhua
    December 24, 2011

    Greate post, one question, what’s the purpose of step 6, set “Symbols Hidden by Default” build setting to NO?

    Reply
    • Christopher Pickslay
      January 19, 2012

      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.

      Reply
  14. Pioter
    February 6, 2012

    Thanks a lot, this helps me.

    Reply
  15. Mickaël
    February 21, 2012

    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… 😉

    Reply
  16. Jasper Blues
    March 2, 2012

    Great tutorial – it’s hard to find this information in the Apple documentation.

    Reply
  17. Jonas Andersson
    March 28, 2012

    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?

    Reply
    • Christopher Pickslay
      March 28, 2012

      It sounds like your unit test target is misconfigured. In the “Build Settngs” tab of your test target, look for the “Mach-O Type” setting under “Linking”. It should be set to “Bundle”.

      Reply
      • Huy Le Duc
        March 22, 2013

        Great tutorial. I’ve spent 8 working hours to find your answer.

        Reply
        • amber
          June 4, 2013

          Thank you so much! This is the answer really solved my problem.

          Reply
  18. Adjeiinfon
    April 13, 2012

    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?

    Reply
    • LP
      April 20, 2012

      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

      Reply
  19. Sam Kim
    August 15, 2012

    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.

    Reply
  20. Stas
    October 9, 2012

    Great tutorial, though I’m a bit stuck on adding logic tests to my app (app tests work fine!) can you please help me and answer my question on stackoverflow.com, here’s the link http://stackoverflow.com/questions/12781589/adding-logic-tests-to-the-project-in-xcode-4-5

    Reply
  21. John Seitz
    November 21, 2012

    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.

    Reply
  22. horseshoe7
    December 29, 2012

    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.

    Reply
  23. Lee Probert
    January 9, 2013

    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.

    Reply
  24. Cocos2D Revised Template « Thought Repository
    January 9, 2013

    […] Next, onto adding Unit Testing. The next bit was inspired by this post: […]

    Reply
  25. Rick
    January 31, 2013

    Great post! This really saved me. I don’t understand why these settings don’t get added by default.

    Reply
  26. Linked to this post on StackOverflow
    April 14, 2013

    I just linked to this article, from this stack-overflow question:

    http://stackoverflow.com/questions/15996347/storyboard-ui-testing-from-code/15998879#15998879

    (one of my fav TwoBit articles)

    Reply
  27. rL
    April 22, 2013

    Thanks so much. I’ve been struggling with this issue for a week now with my setup, and this did the trick!

    Reply
  28. Daniel Beard
    May 11, 2013

    Quick and to the point, thanks!

    Reply
  29. Steffen
    May 20, 2013

    Helped a lot thank you. Was only missing the Test Host setting.

    Reply
  30. Bhaskar
    May 21, 2013

    Fantastic tutorial.. was very worried with the linker errors..

    Reply
  31. dr.alpha
    June 13, 2013

    Perfect, thanks for the post. It’s working like a charm now.

    Reply
  32. HUy
    July 25, 2013

    Thanks a lot. Your post saves me a lot of time

    Reply
  33. Recipe: a Podfile with different Pods per target | Thought Repository
    August 13, 2013

    […] Also assuming you checked the box ‘Include Unit Tests’ when you set up your Xcode project. Otherwise, see here […]

    Reply
  34. Chris
    September 6, 2013

    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?

    Reply
  35. Aqib Mumtaz
    December 6, 2013

    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”.

    Reply
  36. Kle Miller
    April 7, 2014

    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

    Reply
  37. Charles Wang
    September 16, 2014

    Thanks a lot for your help

    Reply
  38. Sebastian Mecklenburg
    February 24, 2015

    For the life of me, I can’t get it to work. Xcode can’t find the executable file I set for the bundle loader, the $(BUILT_PRODUCTS_DIR)/MyExistingApp.app/MyExistingApp path. When I paste the path into the console it is there, so it is correct but the linker stubbornly tells me “File nor found”. What could be the reason for this?

    Reply

Leave a Reply