Why can't I test a basic Multiplatform app with Xcode? - swift

I created the new Multiplatform app with Xcode called Tester and then added a super simple class:
class Transaction {
var time = "1"
}
In the Tests_iOS I have this:
import XCTest
#testable import Tester
class Tests_iOS: XCTestCase {
func testExample() throws {
let t = Transaction()
XCTAssertEqual(t.time, "1")
}
}
The app compiles, however when I try to run the tests, compilation fails with this error:
Undefined symbols for architecture arm64:
"type metadata accessor for Tester.Transaction", referenced from:
Tests_iOS.Tests_iOS.testExample() throws -> () in Tests_iOS.o
"Tester.Transaction.__allocating_init() -> Tester.Transaction", referenced from:
Tests_iOS.Tests_iOS.testExample() throws -> () in Tests_iOS.o
ld: symbol(s) not found for architecture arm64
What am I doing wrong? This is on an M1 Mac using Xcode 13.3

The problem is most likely that you have added your test to the test target that gets automatically created when you create a new multi-platform project and that target is a UI test target and not an ordinary unit test target.
For those tests you do not access the public api of your app like your Transaction class but instead UI components so the test framework has no access to the Transaction class.
The solution is to create a new unit test target, File -> New -> Target... and select Unit Test (remember to select the correct platform because this needs to be done separately for each supported platform).
Once done, move your test to the new target or copy it to the test file that is generated and your test will build and run.

Related

How to add swift unit tests to an Xcode project of type "bundle"?

MacOS 12.5, Xcode 13.4.1
I'm hoping to add swift-based unit tests to an existing project that is Mach-O type "Bundle" (com.apple.product-type.bundle) and acts as a plugin to a third project. Unfortunately I get linking errors when trying to run the unit test bundle such as below.
Underlying Error: The bundle “SOExample Unit Tests” couldn’t be loaded. The bundle couldn’t be loaded. Try reinstalling the bundle. dlopen(/Users/n8henrie/Library/Developer/Xcode/DerivedData/SOExample-fpwfambnggoejceucflywazoligm/Build/Products/Debug/SOExample Unit Tests.xctest/Contents/MacOS/SOExample Unit Tests, 0x0109): Symbol not found: (_$s9SOExample3FooCACycfC)
Referenced from: '/Users/n8henrie/Library/Developer/Xcode/DerivedData/SOExample-fpwfambnggoejceucflywazoligm/Build/Products/Debug/SOExample Unit Tests.xctest/Contents/MacOS/SOExample Unit Tests'
Expected in: '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents/xctest')
I'm using #testable import, have testability set to yes, and am setting the Bundle loader to the bundle's executable.
I've created an example project to demonstrate the issue at https://github.com/n8henrie/so-xcode-unittest-example.git. To create it, I just made a new Xcode project of type "Bundle", added File.swift with class Foo and method bar(), then added a new target -> unit test bundle, which basically tests that it can create a Foo and run bar(). I then add the main target as the Bundle loader and commit changes. One should be able to test as follows:
$ git clone https://github.com/n8henrie/so-xcode-unittest-example.git
$ cd so-xcode-unittest-example/
$ xcodebuild -scheme "SOExample Unit Tests" test
It currently fails with the above "couldn't be loaded" error. It looks like the symbols are there (I think -- I'm new to this):
$ nm -gU SOExample
0000000000003d78 T _$s9SOExample3FooC3barSSyF
0000000000003f34 S _$s9SOExample3FooC3barSSyFTq
0000000000003e14 T _$s9SOExample3FooCACycfC
0000000000003f3c S _$s9SOExample3FooCACycfCTq
0000000000003e4c T _$s9SOExample3FooCACycfc
0000000000003e70 T _$s9SOExample3FooCMa
0000000000008090 D _$s9SOExample3FooCMm
0000000000003f00 S _$s9SOExample3FooCMn
00000000000080c8 D _$s9SOExample3FooCN
0000000000003dd8 T _$s9SOExample3FooCfD
0000000000003db4 T _$s9SOExample3FooCfd
If I change the Mach-O type to Dynamic Library, Static Library, or Relocatable Object File, the linking succeeds and the example test passes.
NB: In this specific toy case I can also get the tests to pass while leaving the Mach-O type as bundle if I add SOExample Unit Tests to the target membership for File.swift; however this doesn't work for my real-world scenario (and I think employing #testable import should make this unnecessary, especially as it works with other Mach-O types).
Is there a way to run my unit tests:
leaving the Mach-O type as Bundle
without modifying "target membership"
?
Relevant links (without solutions that I've found)
Xcode unit tests for plugin bundles
https://developer.apple.com/forums/thread/128330

Swift Unit Test Error: symbol(s) not found for architecture x86_64 (Swift Package Manager)

I am having trouble getting unit tests to run in Swift projects created with the Swift Package Manager (that is, any unit tests created by the Package Manager... those I create from within Xcode work fine from within Xcode). I am getting the same error on all projects generated from the Package Manager. To keep it simple, I tried on a very basic test project with as little modification from the default setup as possible, but still getting the error. Here are the steps to reproduce:
Create a new project with swift package init --type executable (module name is Hello)
Add the Xcode Project: swift package generate-xcodeproj
In Xcode build settings, ensure Enable Testability is Yes
In swift.main enter this simple test code:
import Foundation
let message = "Hello, world!"
print(message)
In HelloTests.swift:
import XCTest
#testable import Hello
class HelloTests: XCTestCase {
func testExample() {
XCTAssert(message == "Hello, world!")
}
static var allTests = [
("testExample", testExample),
]
}
Package.swift and XCTestManifests.swift left as-is.
It builds and runs fine with swift build and swift run Hello (Also, from within in Xcode).
However, when running swift test or running any test in Xcode, the build fails with the following error message:
Undefined symbols for architecture x86_64:
"Hello.message.unsafeMutableAddressor : Swift.String", referenced from:
implicit closure #1 : #autoclosure () throws -> Swift.Bool in HelloTests.HelloTests.testExample() -> () in HelloTests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Somehow, it seems like it's failing to link the main module, so the symbols are not recognized. However, I can't tell what's wrong or how to fix it.
I downloaded one of the sample projects from GitHub, and generated the Xcode project. The tests for this project run perfectly in Xcode and the terminal. I've carefully compared the sample project to mine and can't tell what's different. Almost all setup code (Package.swift, file structure, etc.) and project setting are nearly identical. The only meaningful difference I can tell is that the sample project is a library/framework and mine is an executable (but seems like linking should work the same for both types). Otherwise, I can't tell what they are doing right and I am doing wrong.
I figured it out (thanks to Cristik's help). Executable modules are not testable (at least for now), so the solution was to move all definitions to a library module and leave just the main.swift file in the executable module. That way, all unit tests were run with the library as a dependency vs. the executable. The package.swift now looks like this:
let package = Package(
name: "HighestNumberPairing",
products: [
.executable(name: "HighestNumberPairing", targets: ["HighestNumberPairing"]),
.library(name: "NumberPairing", targets: ["NumberPairing"]),
],
dependencies: [],
targets: [
.target(
name: "HighestNumberPairing",
dependencies: ["NumberPairing"]),
.target(
name: "NumberPairing",
dependencies: []),
.testTarget(
name: "NumberPairingTests",
dependencies: ["NumberPairing"]),
]
)
The full program is here on Github.

How to test a class in a Swift package?

I'm currently trying to understand the mechanism of importing dependencies in a Swift package and I've run into an issue with tests. Hope someone can explain what I'm doing wrong. I'm going to describe the problem step-by-step so that you could easily reproduce it.
So I'm creating a new Swift package using swift package init --type executable. This command creates the basic Swift package structure:
Artems-MacBook-Pro:SwiftExample artem$ swift package init --type executable
Creating executable package: SwiftExample
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/SwiftExample/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/SwiftExampleTests/
Creating Tests/SwiftExampleTests/SwiftExampleTests.swift
Creating Tests/SwiftExampleTests/XCTestManifests.swift
The package itself is called SwiftExample. As you can see the command also creates an example of a unit test case (SwiftExampleTests.swift).
Then I create a simple class called Car.swift and put it into the Sources/SwiftExample/Classes/ directory:
// Sources/SwiftExample/Classes/Car.swift
class Car {
init() {
print("I'm a car!")
}
}
In the main.swift file I can create an instance of the Car class and everything works pretty much fine:
// Sources/SwiftExample/main.swift
print("Hello, world!")
let car = Car()
The output would be:
Hello, world!
I'm a car!
But the problem is I cannot use this class in my test file. For example, I'm trying to create an instance of the Car class in the testExample() function of the SwiftExampleTests.swift file:
import XCTest
import class Foundation.Bundle
#testable import SwiftExample
final class SwiftExampleTests: XCTestCase {
func testExample() throws {
let car = Car()
<other code goes here>
}
<other code goes here>
}
As you can see I've imported the module itself using the keyword #testable. But when I run swift test command I'm getting this weird error:
Compile Swift Module 'SwiftExample' (2 sources)
Compile Swift Module 'SwiftExampleTests' (2 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/SwiftExample
/Users/artem/Playgrounds/SwiftExample/Tests/SwiftExampleTests/SwiftExampleTests.swift:9:13: warning: initialization of immutable value 'car' was never used; consider replacing with assignment to '_' or removing it
let car = Car()
~~~~^~~
_
Linking ./.build/x86_64-apple-macosx10.10/debug/SwiftExamplePackageTests.xctest/Contents/MacOS/SwiftExamplePackageTests
Undefined symbols for architecture x86_64:
"_$S12SwiftExample3CarCACycfC", referenced from:
_$S17SwiftExampleTestsAAC04testB0yyKF in SwiftExampleTests.swift.o
"_$S12SwiftExample3CarCMa", referenced from:
_$S17SwiftExampleTestsAAC04testB0yyKF in SwiftExampleTests.swift.o
ld: symbol(s) not found for architecture x86_64
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)
error: terminated(1): /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-build-tool -f /Users/artem/Playgrounds/SwiftExample/.build/debug.yaml test output:
I'm certainly doing something wrong here but I can't find any information on the matter in the official docs. Does somebody know what's happening here and how to fix that?
Apparently, the issue won't reproduce anymore in the latest Swift version. The Car class can be imported and the swift test command won't fail.
For the record, my current swift --version output is:
swift-driver version: 1.45.2 Apple Swift version 5.6.1 (swiftlang-5.6.0.323.66 clang-1316.0.20.12)
Target: arm64-apple-macosx12.0

How To Link XCTest Dependency To Production / Main Target?

I am trying to write an extension for the XCTest framework in Swift. In order to do so, I created a project with two targets: the main/production target and a test target.
As I am writing an extension for XCTest, I need to import XCTest within my main/production target as well. However, I am having trouble to do so. When in Xcode and I click on my project, then select the main target, go to Build Phases, Link Binary With Libraries and add XCTest there, I get a compile error:
ld: framework not found XCTest
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I also tried the solution provided here which unfortunately doesn't work either.
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!

Swift 3 Unit Testing - Linker fails when using import #testable

I'm currently trying to write unit tests (NOT UI Tests) for my (macOS) Xcode project. I have created a new Unit Test target which creates a blank unit test file for me.
This complies and links fine, but there are no tests.
As soon as I add the line #testable import Pilot, where Pilot is the name of my App Target, and I try to compile and run, it fails with this message:
Linker command failed with exit code 1 (use -v to see invocation)
I've tried everything I can find, but nothing seems to be working. The other posts I have read on here deal with this problem in UI Tests, but that is because you cannot use #testable in UI Tests. You are supposed to be able to use it in Unit Tests, but I can't figure out why this is not working.
Does anyone have any insight?
If it helps, my project is located at: https://github.com/RohanNagar/pilot-osx
Here is the full PilotTests.swift file:
import XCTest
#testable import Pilot
class PilotTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
If your CocoaPods frameworks not included in the Test targets. It will throw this error.
I made sure running a pod install, but still it failed.
So 'pod deintegrate Yourproject.xcodeproj' and reinstall (pod install), clears the issue.
The import fails because the project fails to link together. I downloaded it and I get the following error trying to run it:
Showing Recent Issues
ld: framework not found Realm for architecture x86_64
Try to clean your build folder, or download the project to a new folder, and fix this issue... after that you'll be able to compile and #testable import Pilot.
Set Host Application to your Project Target.
Also, check the Allow testing Host Application APIs is turned on.
Select your test target
Select Build Phases
In the Framework Search Paths, add this
"${PODS_CONFIGURATION_BUILD_DIR}/" with recursive option