When do app sources need to be included in test targets? - ios

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.
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:
There is no difference in the linker output (the command starting with Ld).
There is no difference in the target dependancies (there is 1 dependancy to the test target,which is the app)
There is no difference in the linker settings.

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

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

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.

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.
Also deployment postprocessing should be No for debug.
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.
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.

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

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:

When you create a Unit Testing Bundle(Unit Testing target) for testing application you have two options
Enable Allow testing Host Application APIs
General -> Host Application <app_name> -> >check< Allow testing Host Application APIs
slow build
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>

Related

Swift Compiler Error: use of undeclared type with framework

I'm importing a newer version of a custom framework LCCommLibrary that creates the LCConnection class and I'm absolutely perplexed why this will build but not archive after trying multiple things.
Making sure the targets are added (4 apps, 1 Test)
Cleaning and Restarting the Project
This does builds and runs to my iOS devices, but none of the targets will Archive.
Archive usually uses the Release build configuration while building/running on the device uses the Debug build configuration, this is the hint that you have an issue there.
I would say in your case you can to go into the target's build settings, then look at Other Linker Flags, Runpath Search Paths and Library Search Paths, expand them to show the Debug and Release configurations and then ensure you have the same settings for both of them, you'll most probably find something missing in release in one of those.
Finally found the issue. The culprit was framework that was included was a debug version of the framework. This causes Archive Schemes to throw this error since the archive uses the Release as the default.
The resolution was to open the LCCommLibrary Project separately, change the Framework Target's Run Scheme to Release, Run and Build, Locate the target framework, import and embed that back into main project file, and archive as usual.

Xcode unittest build failed with error "Undefined symbols for architecture x86_64"

My unittest target build failed with below error:
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_MCStore", referenced from:
objc-class-ref in MCStoreTests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Basic information:
Xcode6.2
iOS8.2 SDK
What I have checked:
Symbols Hidden by Default is No
Other Linker Flags is -framework XCTest
Framework Search Paths is $(SDKROOT)/Developer/Library/Frameworks $(inherited)
According to this link, I need to set Bundle Loader with below content in unittest target Build Settings
$(BUILT_PRODUCTS_DIR)/MyExistingApp.app/MyExistingApp
At least as of Xcode 7.3 test targets let you select a "Host Application". In the Test target (but not presently the UI Test target) this automatically populates the "Test Host" build setting, but not the "Bundle Loader", which can lead to classes not being found.
Considering this, if you set your test targets' "Bundle Loader" Build Setting to $(TEST_HOST), it will always contain the right value even if you change the Host Application.
This is effectively the opposite of the advice given in the link #yuwen-yan posted, and should amount to less work.
This error may be the result of having wrong test target type, namely ui test target.
UI test targets can't use the internals of the main target, not even with #testable imports. Unit test targets OTOH can use the internals.
See more details in this answer.
(I believe this has changed in some XCode version which causes confusion. Typical way is just to include a huge bunch of files from the original target in the ui test target. A proper way is to design UI tests in such a way that they don't need or use much code from the main target.)
In my case,I got this error when I need to test class that exist in framework that was part of my app :
testing framework class.
1. Add testing target to macOS .
2. Select project/targets window. Select the test target.
3. Go to “build phases”, “link binary with libraries”, add the framework you want to test .
4. In the test class (setup/teardown), add “import” to the class you want to test from that framework
Experienced the same issue.
What fixed it for me was setting enable modules (c and objective-c) to YES in the Testing target Build Settings.
In my particular case I was trying to test release configuration and was getting this particular error. After experimenting with different compilation flags I found out that setting Enable Testability for release configuration on my projects (not particular targets but might work too) did the trick.
Xcode 12.3 (12C33) – I just removed the testing targets and all references. Then created fresh new test targets, and, added back all my test files ✓ just works
What worked for me was just using the test target on the podfile.
target 'MyAppName' do
use_frameworks!
pod 'SwiftLint'
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxDataSources'
pod 'RxGRDB'
target 'UnitTestTarget' do
inherit! :search_paths
end
end
Experienced the same linker error after adding test target to old project which was created 2 or 3 Xcode versions ago. Furthermore, project has various xcodeproject/target/bundle names. All possible renames, cleanings, Build settings, Build phases, Scheme manipulations did not worked for me.
What did actually worked after a long struggle is recreating the project along with all targets from scratch in latest Xcode version. It finally links! And in this case you don't even need to manually modify Search paths, Bundle loader, Xcode will do it for you.
My two cents for people like me getting this error using new(2019..) SPM.
I got the same error importing a package in an (old) app.
As importing packages force You to choose target:
Doing so, you don't have it in Tests, unless You:
a) add Host application too test targrt
b) flag "Allow testing Host Application APIs"
as here:
now remains the question how to test an app without running App too during testing....
I had a unit test target which would not run because of missing symbols.
What fixed it for me was going to
Project -> Targets -> myTestTarget -> Build Phases -> Link Binary With Libraries
and adding the missing library there. I'm using SPM to manage this dependency, probably it was not added properly.
For me I got a similar error message when I tried writing and running unit tests in a project that previously only had UI tests. After many hours of troubleshooting, I realized the problem was that my current testing target was only for UI tests, not unit tests.
To fix, I just needed to add a new Unit Testing Bundle target, and then make sure that each unit test file I was trying to run had its Target Membership set to the new target I just created.
Below are the steps that I performed to fix the issue while trying to add unit test target in Xcode 9:
Go to 'Manage Schemes'.
Click on '+' button at the bottom.
Select the newly added Target and select 'OK'.
Make sure 'Shared' option is selected for the newly added target.
So here is what worked for me...
override func setUp() {
super.setUp()
let promise = expectation(description: "App has finished running")
DispatchQueue.global(qos: .background).async{
// Wait on the background thread
sleep(4)
DispatchQueue.main.async {
// Fullfill the promise in the main thread
promise.fulfill()
}
}
// Initialize the storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// Get the view controller
sut = storyboard.instantiateViewController(withIdentifier: String(describing: ViewController.self)) as? ViewController
_ = sut.view
waitForExpectations(timeout: 5) { (_) in
// Finish set up after the app is done running its code
}
}// End setUp() Method

How to unit test an app extension on Xcode 6

Does anyone know how to perform unit testing on app extension target, especially keyboard extension target?
What have I tried (in the unit test target):
In the "General" tap, set it's target to the extension target instead of the container app.
Set the "Bundle Loader" to the path of the binary of the extension target, which looks like $(BUILT_PRODUCTS_DIR)/com.mycompany.keyboard.appex/com.mycompany.keyboard
Set the "Test Host" to $(BUNDLE_LOADER).
In the "Build Phases" tap, set the "Target Dependencies" to both the container app and the extension.
After these things done, I can build it successfully but always get "Test Failed" with an log Test target SogouInputTests encountered an error (Test session exited(1). without checking in. If you believe this error represents a bug, please attach the log file at /tmp/TestStatus-UXfvxw.log).
I'm using Xcode 6 beta 3.
I have reported the bug to Apple. And sadly, the answer is that the keyboard extension is not support unit test now. The answer comes from Apple:
It's not currently supported to run unit tests inside an app extension
Instead, factor the code you want to test into a framework and test the code there
Link the framework into your extension
Just ran into similar issues trying to unit test an extension. Independently did exactly the same thing the author tried with Bundle Loader pointing to .appx path with no success of course.
I really did not like the idea of creating a separate framework just for testing so I ended up of adding testable source into the extension test target. It is really simple if you don't have too many source files in your extension:
Select you extension test target in Project settings
Go to Build Phases
Expand Compile Sources
Click +
Add source files with your testable code.
Build for Testing
Why it works:
Once you add extension sources into your extension test target, XCode is going to double reference each of them and compile in both the normal extension build and the test build thus eliminating linking issues.
Are there any drawbacks?
You will have to manually synchronize the source file list in the extension test target. Whenever you add/remove files in the extension target, you may need to do the same in its test target.
What I did which was easier than the other suggestions (no framework creation, no weird settings on build settings, phases, etc) was adding the file that I wanted to test (from the extension target) into the Target Membership of the test target:
The only "drawback" would be that your test target will also include files from your extension (rather than using #testable import like with the main app), but since you are not shipping your test target I would say there is no drawback :)
Tested on Xcode 10.
Note: Only works with Swift code, with ObjC due the BridgingHeader is not possible to follow this approach.

XCTest build errors for test target Xcode 5:

I have set up an XCode 5 iOS 7 project for unit tests.
Of course, setting up the unit tests are taking me so long that I'm trying to keep the faith that it's worth it. Struggling for hours over this error:
ld: building for iOS Simulator, but linking against dylib built for MacOSX file
'/Applications/Xcode5-DP5.app/Contents/Developer/Library/Frameworks/XCTest.framework/XCTest'
for architecture i386
Any ideas on how to solve?
Check your Framework Search Paths in your test target settings. These can be corrupted when adding the XCTest Framework.
Adding XCTest to one of my projects prepended a "/" to the paths causing them to not find the correct version.
None of the above answers worked for me. I did find an answer here in a comment left by Tim Macfarlane.
For linker errors looking for a class in your app...
set the “Symbols Hidden by Default” build setting to “NO” in your app
target. This makes all your app classes available to your test target
automatically...
So, that means:
Project Navigator > Select your project
Targets > Select your App (not Tests)
Build Settings > Search for "Symbols Hidden By Default"
"Symbols Hidden By Default" > Change it from "YES" to "NO"
I had the same issue; the problem (for me, at least) was that the FRAMEWORKS_SEARCH_PATHS build setting listed the SDK frameworks folder after the main developer frameworks folder.
The frameworks included with Xcode have three separate builds: one for OS X, one for iOS (device), and a third for the iOS Simulator. The OS X build is in the main developer folder, with the other two being under their respective platform folders. The rub here is that if you don't specify to search the SDK folders first (which are within the platform folders), Xcode (or more correctly, the linker) will find the OS X build first and produce the error you see.
The solution is simple, put:
FRAMEWORK_SEARCH_PATHS = $(SDKROOT)/Developer/Library/Frameworks $(inherited)
in your build settings. If you're putting build settings in the project file (I don't recommend it, but that's another question for another day), it's just named "Framework search paths."
NOTE: Sometimes Xcode's a little slow to catch on; you'll probably need to delete your build folder (better than just a clean) for this to take effect.
Have the same problem after converting tests from SenTestCase to XCTestCase. Reverting framework dirs fixed issue:
"$(SDKROOT)/Developer/Library/Frameworks" (non-recursive)
"$(DEVELOPER_LIBRARY_DIR)/Frameworks" (non-recursive)
So, for me, what I was missing after trying everything else in this post, was:
Other Linker Flags:
-framework XCTest
I'm currently using Xcode 6.0 (with the iOS 8 SDK) so I'm surprised that the "Edit > Refactor > Convert to XCTest..." option doesn't add this automatically.
I was facing problem while adding sentestingkit framework in xcode 5 . These settings worked for resolving linker problem.
I had this problem upon adding another file for tests.
If you do this with (CMD + N) be sure to only target
the Test Bundle (ie. 'AppNameTests').
I guess only these .xctest bundles have access to the
XCTest Framework.
I had the same issue after renaming my Target name and moving things around. It turned out that my tests were part of my Main Target. Make sure that you all your test files belong only to your test target.
Just select a .m file, make sure you have the right pane open.
I had the same problem when tried to build XCTTest-based unit tests with pre-7.0 SDK. When I chose 7.0 as my Base SDK then that kind of link error disappeared.
Had a the same issue but ended up with a slightly different solution.
select XCTest.framework and make sure that only your test folder is checked under Target Membership.
Make sure that the Search Framework Path (FRAMEWORK_SEARCH_PATHS) for the YourProjectTests target includes the path $(SDKROOT)/Developer/Library/Frameworks, and that this one is listed before $(inherited).
In my case, both paths were present, but $(inherited) was the first one.
Credit goes to https://stackoverflow.com/users/181947/brian-clear on Apple Mach-O linker (id) warning : building for MacOSX, but linking against dylib built for iOS

No architectures to compile for error when trying to set up unit testing in existing project

I am currently working on an existing iOS project in Xcode 4, and I wanted to add unit-testing using Xcode's built-in unit testing framework (OCUnit).
I followed this guide http://developer.apple.com/library/mac/#documentation/developertools/Conceptual/UnitTesting/02-Setting_Up_Unit_Tests_in_a_Project/setting_up.html on setting up unit testing, but when I try to run the tests, I get the following error:
No architectures to compile for (ARCHS=x86_64, VALID_ARCHS=armv7 armv7s).
I really don't want to migrate everything to a new project, because this project is a very large and unweildy one. However, I really do want to add unit testing.
Thanks in advance!
After trying to add unit tests several times over, I discovered that in the page for my test target, under build settings -> architectures, there is an entry named valid architectures. I tried messing with the architectures setting, and that did not work, but changing the valid architectures for the unit test target to
$(ARCHS_STANDARD_32_64_BIT)
made it compile!
I still have to deal adding missing frameworks in the test target, but this approach answers my main question.

Resources