How to unit test an app extension on Xcode 6 - ios

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.

Related

Can't access swift files in Unit test target in Xcode

Adding swift files to test target will work, but it is not the best way to do. My problem is I can't able to access Swift file whereas Objective-C files are accessible.
I have checked product module name and set configuration file same as project file for test target. Even removed the test target and readded, but, still encountering "Use of undeclared type in SlideViewController".
Can anyone help me with solving this issue?
By default you won't be able to access internal classes from your unit test target.
The apple docs on writing tests with swift say that you need to take two steps to get around this:
Set the ENABLE_TESTABILITY build setting to YES.
Add #testable to the import statement for your module. #testable import MySwiftApp
If you follow both of those steps your SlideViewController (as long as it is not a private class) should be accessible from your unit test file as if it was declared as an open class.
Try to add this on top of your test file:
#testable import <YOUR_MODULE_NAME>
Sometimes you will need to add files to the build phases of your test target.
1.- Go to project navigator
2.- Select your project
3.- On the project and target list, select the target for your tests (Ex. "MyProjectTests")
4.- Select Build Phases tab
5.- Open "Compile Sources"
6.- Using the plus sign, add the files needed for the compilation

Can't access app code from XCTest

I have a growing swift project that I have been writing both unit and UI tests for along the way. My UI tests run just fine, but my unit tests have stopped compiling. I am using "#testable import X", but anytime I try to access a class from the project, even if I make that class public, I get a "Use of unresolved identifier" error. I have no idea what I could have changed to cause things to start failing.
Choose "Clean" from the Xcode "Product" menu and then recompile. That will often get it working again. For some reason, we have to "Clean" before the classes are made available to the test target.
My original answer below outlines the old solution before #testable was available to us.
--
Make sure the source file for PostCell is included in the list of source files for the tests target.
You can do this by going to the tests target and adding it to the list of "Compile Sources":
Or by clicking on "Target Membership" to the "File Inspector" for the source in question:

Adding test target to an existing project is not working

I need to add test cases to an existing project, so I tried adding a new test target via
File -> New -> Target -> Cocoa Touch Testing Bundle
From the test navigator filter bar.
Even after adding, the target is not getting listed in the test filter bar.
May be because of this issue, Product -> Test option is diabled in my project.
Also I could see some differnce between the build setting in my project and another sample project I started in Xcode 6.1 in which Product -> Test option is enabled and everything working fine for Test cases.
Please find the build settings screen shots.
Any help is appreciated.
I had this exact problem. It turns out my existing project was creating my Cocoa Touch Testing Bundle with the .app extension when it should've been a .xctest extension.
You can fix this by going to your test target you created. Build Settings > Wrapper Extension and changing it from app to xctest.
You may have to restart Xcode after this change to select your test target.
UPDATE: I found out the reason the Testing Bundle was becoming a .app extension in the first place. I had to go to the main project Wrapper Extension and make sure it was blank instead of .app. Apparently that was forcing all targets to become .app extension as well.

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

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>

Unit Testing in With A Static Library

I have an XCode workspace with a user interface project (UI) and a core logic project (Core). I want OCUnit unit tests in the UI project, so I have added a new target for testing, as is commonly done.
I am able to run tests just fine until I put in import statements for classes in the main UI target which in turn reference the Core project.
The error I get is "Lexical or Preprocessor Issue 'xxx.h' file not found". I do not get this message when I build the main UI target directly.
It's as if the main UI target knows about Core when it is built, but when it is referenced from the test target it seems to know nothing about Core.
I took the step of adding a reference to the core project using the "Link Binaries with Libraries" The item in the list remains red. A clue? Maybe, but the red reference in the Link list doesn't keep the UI target from building and using core classes. I also made the main target a dependency of the test target.
Make sure you check out the Apple sample code "Unit Tests":
https://developer.apple.com/library/ios/#samplecode/UnitTests/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011742
Make sure your library project is set as a Dependancy in your OCUnit test target's build phases, AND it's linked as a library.
Open up your project in Xcode. In the File menu, go to Project Settings... (or Workspace Settings... if you are using a workspace). Click Advanced... and make sure Unique is checked. Clean and rebuild.
Check your BUILD_PRODUCTS_DIR to see if the headers for your library show up in there. If they don't, first check the build phases in your library target to make sure the headers you need are in the Public section (the Project section may work as well, but try Public and see if that solves your issue).
That covers the most common problems people seem to run into in your situation. When in doubt, check the target settings in the UnitTests sample against yours. Good luck!
In addition to Jon Reid's answer, I had to do the following as well:
In your test target, go to Build Settings. Set "Always Search User Paths" to YES
In your test target, go to Build Settings. Add the path to your static library headers to Header Search Paths.

Resources