When do app sources need to be included in test targets?
Asked Answered
T

7

73

In a new project I have this simple test

#import <XCTest/XCTest.h>
#import "ViewController.h"

@interface ViewControllerTests : XCTestCase
@end

@implementation ViewControllerTests

- (void)testExample
{ 
    // Using a class that is not in the test target.
    ViewController * viewController = [[ViewController alloc] init];
    XCTAssertNotNil(viewController, @"");
}

@end

ViewController.h is not part of the test target yet this compiles and runs the tests with no issues.

enter image description here

I think this is because the application is built first (as a dependancy) then the tests. The linker then figures it out what the ViewController class is.

However, on an older project, with exactly the same test and ViewController file, the build fails at the linker phase:

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

This linker error occurs even if when a fresh XCTest unit testing target is created.

To get around this instead, it is possible to include the sources in both the app and the test targets (tick both boxes in the image above). This causes build warnings for duplicate symbols, in the simulator's system log (open the simulator and press cmd-/ to see this):

Class ViewController is implemented in both 
[...]/iPhone Simulator/ [...] /MyApp.app/MyApp and 
[...]/Debug-iphonesimulator/LogicTests.octest/LogicTests. 
One of the two will be used. Which one is undefined.

These warnings occasionally cause issues illustrated by the following example:

 [viewController isKindOfClass:[ViewController class]]; // = NO
 // Memory address of the `Class` objects are different.

 NSString * instanceClassString = NSStringFromClass([viewController class]);
 NSString * classString         = NSStringFromClass([ViewController class]);

 [instanceClassString isEqualToString:classString]; // = YES
 // The actual class names are identical

So the question is what setting(s) in the older project are requiring application source files to be included in the test target?


Summary of comments

Between the working and the non-working project:

  1. There is no difference in the linker output (the command starting with Ld).
  2. There is no difference in the target dependancies (there is 1 dependancy to the test target,which is the app)
  3. There is no difference in the linker settings.
Tootsie answered 20/2, 2014 at 14:46 Comment(14)
There is probably a problem in the test target settings. Could you show the settings of your test target?Haemophilic
@Haemophilic - Thanks for your response. There are around 200 build settings per target. Do you know which ones might be relevant?Tootsie
Linking and dependencies. Sharing a sample project with the problem would be the best solution.Haemophilic
@Haemophilic - Unfortunately I cant share the project. I have verified that this still happens on the old project even when I create a fresh XCTest target, so I guess the issue is in a project setting. The target dependancies are identical from working to non working (1 item which is the app). The Linker settings were identical apart from 'Other linker flags' which was -framework XCTest in one and ObjC in the other. I corrected this difference and it still failed to compile :( Can you think of anything else?Tootsie
Check the linker output directly (the rightmost tab in the project navigator), the link step. Check differences between arguments passed to the linker.Haemophilic
@Haemophilic - Good suggestion, I am going to look at the linker output shortly and ill let you knowTootsie
@Haemophilic - There were two differences in the linker commands (Starting with Ld). The first was these 2 additional flags: -fprofile-arcs -ftest-coverage. I deleted these 2 options and added it made no difference. The other was -F/Applications/Xcode.app/Contents/Developer/Library/Frameworks was repeated 4 times in a row in the non working project, but only 2 times in a row on the working project. However I don't think that will be it (its is probably included once per framework linked).Tootsie
I have provided a reasonable amount of information in the question and responded to all queries for more information. So I am at a loss as to why this is on hold. I would be grateful for some more reopen votes as I would like to open a bounty on this question.Tootsie
Unfortunately, we have no way to reproduce the problem. That's why we had to close. Including a minimal example of the problem should be easily possible.Haemophilic
Unfortunately, I can't share the whole project. If I knew what settings are required to make a failing minimal project then I wouldn't need to ask the question. I would like to open a bounty as this might attract some more attention and hopefully some fresh insight.Tootsie
Just remove all the files from the project, add a sample controller, check that the error is still there and share that.Haemophilic
@Haemophilic - Another good suggestion, however this turned out not to be possible. The project has many dependancies, files, targets and settings to rename as well as the sources to remove. XCode is unable to perform the necessary changes, its crashing when trying to use the auto-refactoring tools. Performing the changes by hand would take too long.Tootsie
Another suggestion, under the Unit Testing section of Build Settings, is there a difference in the value of Test Host?Socialist
Someone should update this question because it's really way more complicated than it needs to be: for sure it is wrong to include the source files in the test target, and especially with projects that contain Swift, there are other things you need to do to make the build work properly, but the path to getting this to work, with a Framework, was not helped by this thread, so this is probably in retrograde at this point.Protectionist
C
51

I spent some time figuring this out.

If you read this documentation you find that Xcode has two modes for running tests. Logic Tests and Application Tests. The difference is Logic tests build their own target with your Classes and symbols built right in. The resulting executable can be run in the simulator and reports test output back to Xcode. Application tests on the other hand build a dynamic library linking to your code which is injected into the app at runtime. This allows you to run tests in iPhone environment and test Xib loading and other things.

Because the symbols are missing from your test target when you unlink the source files it appears your older project seems to have a test target configured for logic tests, not Application (unit) tests.

As these days Xcode seems to be trying not to distinguish between the two and defaults to creating an Application Tests target lets walk through all the things you might have to change to turn your Logic Test Target into a unit test one.

I'm also going to assume that you have an Application Target and not a static library target as the directions will be a little different.

  1. In the build settings for your test target delete "Bundle Loader" and "Test Host" build settings. We will get Xcode to add these back later
  2. You need to remove all the .m files from your application from the test target. You can either do this by selecting all the .m files and removing the test target in the Xcode File inspector or you can use the compile sources build phase of the test target.
  3. Change the "Framework search paths" for your test target. For Xcode 5 they should be $(SDKROOT)/Developer/Library/Frameworks $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) in that order and with no extra quotes or backslashes
  4. Go to the General pane of your test target's build settings and select your target from the drop down menu. If the menu already specifies your application target you should toggle it off and on again. This will make Xcode reconfigure the Bundle loader and Test Host settings with the correct value.
  5. Finally double check your application's scheme. In the scheme drop down select edit scheme. Then click the test action. Make sure you test target is in the list on the info pane and make sure all the tests are selected.

This information more or less comes from the above linked documentation, but I updated the steps for Xcode 5.

EDIT:

Hmm 100% note what eph515 is saying about debug symbols being visible but you might also want to check that someone didn't set your scheme's test action to build in the Release or other configuration. Click the scheme selector an choose edit scheme. Click the test action and then make sure the Build Configuration is Debug

build configuration screen for test action in a scheme

If you have a Static Library Target

So if you have a static library target you have two options: 1. Logic Tests 2. Application tests in a host app

For 1. you have to make sure that Bundle Loader and Test Host are empty for your static library target. Your sources then have to be compiled into the test target as they would have no other way to be run.

For 2. You need to make a new app Project in Xcode and add your static Library project as a subproject. You then need to manually copy the Bundle Loader and Test Host build settings from your New App's test target to your Static Lib test target. Then you open the scheme for your new Test App and add your test target to the tests action for the new app. To run the tests on your lib you run the test action for your host app.

Cruelty answered 25/2, 2014 at 19:37 Comment(6)
Thanks for you answer, this looks good. Ill investigate tomorrow.Tootsie
I did all the steps but it failed. I even made a new application target (which automatically generated a new application testing target) in the existing workspace and it still failed. I verified the Framework search paths and re-generated the Bundle Loader and Test Host as described. My guess now is that there is there is an inherited workspace setting that is messing it.Tootsie
"I'm also going to assume that you have an Application Target and not a static library target as the directions will be a little different." Can you tell me where to find instructions for a static library target?Tootsie
I'll add to my answerCruelty
Iv accepted this answer but given the bounty to eph515 answer. This is the correct answer, but eph515's helped me find my specific issue.Tootsie
Wish I could up-vote your answer 100x! :) Saved my day - Thanks a lot!Nidifugous
G
22

On Xcode 6, I was able to fix this problem by checking "Allow testing Host Application APIs" in the test target > General > Testing.

Xcode Screenshot

Greenleaf answered 12/8, 2014 at 1:46 Comment(4)
Interesting - thanks! Was this for an old existing project? Is this selected by default for new projects?Tootsie
This worked for a project created in Xcode 5, with the test target being added later in Xcode 6. I don't know if it's selected by default for new projects.Greenleaf
Thanks, this fixed my app project created with Xcode 5. But "Host application" selection is not applicable for my static library that still has "Undefined symbols for architecture" error (after following instructions by @Cruelty etc). Any more clues to fix tests for static libraries with Xcode 6?Bathsheba
@clance_911 also be sure single object prelink is offIndue
C
18

I ran into this as well and followed jackslash's recommendation but with one more addition: Select your main target and looks for Symbols Hidden by Default (under Apple LVM 5.0 - Code Generation), if the value is Yes, change it to No. This seems to 'un hide' all the symbols of the compiled sources that the unit test target is looking for. Works for me. Please make sure that you include all the steps that jackslash outlined as well.

Christoper answered 26/2, 2014 at 23:53 Comment(5)
Notice that the iOS default for "Symbols Hidden by Default" seems to be NO for Debug builds and YES for Release. Your project should probably match the defaultsCruelty
If theres something to do with symbols also check "Strip Debug Symbols During Copy" which should also be NO for Debug and YES for ReleaseCruelty
Yes, I believe the 'newer' XCode projects had the Symbols Hidden by Default set to NO for Debug, since I think XCode 5 now includes unit test by default or if the option to choose to include unit test is chosen in Xcode 4. For projects created by XCode 4 and prior, without the inclusion of unit test, that value is YESChristoper
The answer was to change this flag plus the Deployment Postprocessing to NO for debug. I also had to fiddle with the liked libraries to avoid duplication. Iv given the bounty to this answer since I wouldn't have go though all the build setting otherwise.Tootsie
This was the correct answer in my case which at first sight is the same as the described in the question. I went to the 'Build Settings' of the main target and under 'Apple LLVM 6.1 - Code Generation' set 'Symbols Hidden By Default' to 'No'. @Christoper Thank you!Shana
T
9

The answer was a combination of jackslash's and eph515's answers.

As in eph515's answer symbols hidden by default should be No for debug.

enter image description here

Also deployment postprocessing should be No for debug.

enter image description here

Also all libraries that are included in the test target should be removed from the unit test. All that should be left are the 3 in the screen shot plus anything that is specific to unit testing.

enter image description here

Also if there is a run build script build phase at the end of the list, then it should be removed (since it is an artefact of unit testing).

Then do everything in jackslash's answer.

Tootsie answered 3/3, 2014 at 17:7 Comment(2)
All in all I think this will be a very informative and constructive SO question for future reference :)Cruelty
Thanks for summarizing and centralizing, this worked.Sulphuryl
D
0

In my case in Xcode 6.2 was error in different architectures in project target and tests target.

Project target has only armv7 and armv7s architectures (because of some older library)

Project Tests target has armv7, armv7s and arm64 architectures.

Removing arm64 architecture solve this issue for my case.

Project Editor -> Project Tests target -> Build Settings -> Valid Architectures = armv7 armv7s

(perhaps it is needed also to set "Architectures" instead of $(ARCHS_STANDARD) to $(ARCHS_STANDARD_32_BIT))

Diablerie answered 14/4, 2015 at 17:46 Comment(1)
Removing ARM64 will disable your support for some apple devices such as iPhone5 and iPad: jamesdempsey.net/ios-device-summaryRictus
G
0

For me it was just a case of having no test targets added for the Scheme.

For the app target go to Edit Scheme, then click Test on the right hand side then add a test target with the + button at the bottom: enter image description here

Galvan answered 25/5, 2017 at 12:52 Comment(0)
D
0

When you create a Unit Testing Bundle(Unit Testing target) for testing application you have two options

  1. Enable Allow testing Host Application APIs
General -> Host  Application <app_name> -> >check< Allow testing Host Application APIs 
  • slow build
  1. Add every app's tested file into Target Membership[About]
  • you should take care of classes dependencies which are used into testable class(they should be added too)

When you write a test and no one option was not enabled you can get

Undefined symbol: nominal type descriptor for <class_name>
Undefined symbol: type metadata accessor for <class_name>
Doorway answered 3/10, 2020 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.