How To Link XCTest Dependency To Production / Main Target?
Auxiliary information on XCTest itself is sparse and hard to find, I was also chasing down the same functionality and finally managed to get it working.
I am on XCode 10.1 and running on a real iPhone with iOS 11. I am certain the general technique will work for other versions, but probably will require a few tweaks.
The general steps are described in this stackoverflow answer, but required several additional steps and tweaks to work for me on a real iPhone:
Is it possible to run XCTest tests in an iOS app?
Follow the steps in the above link. The below steps are deviations from those instructions that were required for me.
Copy in the XCTest framework as described in the above link. NOTE: Use the framework for the
iPhone.OS
platform and not the simulator as it describes. You can find this framework file inside the actual XCode Application package on your mac. (Right click, "Show Package Contents", then look in./Contents/Developer/Platforms/iPhoneOS.platform
Disable bitcode in your app target. This solves a linker error. Here is an example of enabling it: how to ENABLE_BITCODE in xcode 7?
When dragging the
XCTest.framework
file to the linked binaries in your target, ensure that you also drag it to the "Embedded Binaries" which is directly above the "Linked Frameworks and Libraries" option. If you don't do this you will get a runtime error.
- The ViewController code to start the tests is slightly different in new Swift, here is what I am using:
import UIKit
import XCTest
class ViewController: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("running tests!")
let suite = XCTestSuite.default;
for test in suite.tests {
test.run()
}
}
}
That should be it! When I run the above app, then touch the screen, all of the tests from my UITesting target run flawlessly and pass!
Using XCTest in non-Test Target
You should add $(PLATFORM_DIR)/Developer/Library/Frameworks
into Framework Search Paths
which in the Build Settings of your framework target. Basically, you don't have to link your framework against XCTest.framework
.
Also, if you're going to share the framework via pods you can add following code to your podspec file:
Pod::Spec.new do |s|
...
s.weak_framework = "XCTest"
s.pod_target_xcconfig = {
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"',
}
...
end
Using XCTest in non-Test Target
You should add $(PLATFORM_DIR)/Developer/Library/Frameworks
into Framework Search Paths
which in the Build Settings of your framework target. Basically, you don't have to link your framework against XCTest.framework
.
Also, if you're going to share the framework via pods you can add following code to your podspec file:
Pod::Spec.new do |s|
...
s.weak_framework = "XCTest"
s.pod_target_xcconfig = {
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"',
}
...
end
Is it possible to run XCTest tests in an iOS app?
It is possible! The key is to get a copy of the XCTest framework linking and then use the public XCTest APIs to run your tests. This will give you the same console output you see when running the tests via Xcode. (Wiring up your own, custom reporter looks doable using the public APIs, but asking how to do so would make a good question in itself - not many people use the XCTest APIs, because Xcode does the dirty work for us.)
Copy In XCTest.framework
Rather than unravel whatever is breaking the module loading, you can copy the XCTest.framework bundle into your project from the platform frameworks for your target platform:
mkdir Frameworks && mkdir Frameworks/iphonesimulator
PLATFORMS=/Applications/Xcode.app/Contents/Developer/Platforms
FRAMEWORKS=Developer/Library/Frameworks
cp -Rp \
"$PLATFORMS/iPhoneSimulator.platform/$FRAMEWORKS/XCTest.framework" \
Frameworks/iphonesimulator/
Link Against Your Own XCTest
Then, pop open the target editor for your main app target, and drag and drop your copy from Finder to the list of "Linked Frameworks and Libraries".
Build Your Tests Into the Main App Target
Now, go to your test files, pop open the File Inspector, and tick the box next to your main app target for those files. Now you're building the test files as part of your main app, which puts all your XCTestCase
subclasses into the binary.
Run Those Tests
Lastly, wire up a button to "Run Tests" to an action like this:
import UIKit
import XCTest
class ViewController: UIViewController {
@IBAction func runTestsAction() {
print("running tests!")
let suite = XCTestSuite.default()
for test in suite.tests {
test.run()
}
}
}
When you tap the button, you'll see this in the console:
running tests!
Test Suite 'RunTestsInApp.app' started at 2017-05-15 11:42:57.823
Test Suite 'RunTestsInAppTests' started at 2017-05-15 11:42:57.825
Test Case '-[RunTestsInApp.RunTestsInAppTests testExample]' started.
2017-05-15 11:42:57.825 RunTestsInApp[2956:8530580] testExample()
Test Case '-[RunTestsInApp.RunTestsInAppTests testExample]' passed (0.001 seconds).
Test Case '-[RunTestsInApp.RunTestsInAppTests testPerformanceExample]' started.
/Users/jeremy/Workpad/RunTestsInApp/RunTestsInAppTests/RunTestsInAppTests.swift:34: Test Case '-[RunTestsInApp.RunTestsInAppTests testPerformanceExample]' measured [Time, seconds] average: 0.000, relative standard deviation: 122.966%, values: [0.000002, 0.000001, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[RunTestsInApp.RunTestsInAppTests testPerformanceExample]' passed (0.255 seconds).
Test Suite 'RunTestsInAppTests' passed at 2017-05-15 11:42:58.081.
Executed 2 tests, with 0 failures (0 unexpected) in 0.256 (0.257) seconds
Test Suite 'RunTestsInApp.app' passed at 2017-05-15 11:42:58.081.
Executed 2 tests, with 0 failures (0 unexpected) in 0.256 (0.258) seconds
Related Topics
How Are Optional Values Implemented in Swift
Swift Codable Decode Manually Optional Variable
(Swift) Nstimer Stop When Scrolling
Subclass.Fetchrequest() Swift 3.0, Extension Not Really Helping 100%
Using Swift to Disable Sleep/Screen Saver for Osx
Realm Mobile Platform, How to Connect While Offline
How to Check If 'Any(Not Any)' Is Nil or Not in Swift
Hex String to Character in Pure Swift
Testing an Executable with Swift
Swift: How to Access in Appdelegate Variable from the View Controller
Swift Property Observer in Protocol Extension
Reading an Inputstream into a Data Object
How to Get Day and Month from Date Type - Swift 4
Why Does My Version of Filter Perform So Differently Than Swifts
How to View Value of Swift "Let" Constant in Xcode 6 Debugger