Swift test give error "Undefined symbols for architecture x86_64"

12.7k views Asked by At

I'm running swift test from the command line to run the test cases. This is the test case:

import XCTest
@testable import vnk_swift

class KeyMappingTests: XCTestCase {
    static var allTests : [(String, (KeyMappingTests) -> () throws -> Void)] {
        return [
            // ("testExample", testExample),
        ]
    }

    func testExample() {
        let keyMapping = KeyMapping()
        XCTAssertNotNil(keyMapping , "PASS")
    }
}

And here is the output message.

image

If I remove the usage of KeyMapping, everything works fine:

    func testExample() {
        // let keyMapping = KeyMapping()
        XCTAssertNotNil(true , "PASS")
    }

image

Looks like there is a problem when I'm trying to use a class. How do I fix this?

(I did not use Xcode for this project as I started with swift package init, the source code for this project is here: https://github.com/trungdq88/vnk-swift)

9

There are 9 answers

4
Cristik On BEST ANSWER

I managed to successfully build and test your package by doing the following modifications:

  • renamed the package name to VnkSwift, for some reasons the build tool doesn't like dashes in package name, nor it works when you have underscores in the generated package name (so renaming the package to vnk_swift to make sure the import statement and the package name match didn't work)
  • renamed the test folder to VnkSwiftTests in order for the linker to know what to link against; seems this is a precondition for the linker to know to link against the package
  • finally, renamed main.swift to something else (I used utils.swift). The thing is that the presence of main.swift instructs the build tool to generate an executable, and linking against an executable doesn't work very well. After the rename, had to comment the if code, as global running code can only belong to main.swift.

To conclude:

  • Avoid non-alphanumeric package names
  • Package name and test directory name must be in sync
  • Make sure you don't have a main.swift file to make sure the package can be linked against

Why can't a unit test target link against executables?

It's because both the test bundle and the executable bundle have global executing code (aka the main function), so the linker doesn't know which one of them to pick. When testing from Xcode, the test bundle runs into the context of the application - it doesn't link against it, like in the situation right here.

Xcode also has this problem when creating a command line tool - you can't unit test that target, if you want to unit test the code then you have to create a library that will be linked by both the tool and the unit test target (or include the files in both targets)

2
Alistra On

I see that in the directories you have vnk-swift, but in import statements and in the mangled name you have vnk_swift. Maybe this is an Xcode/compiler bug with handling hyphens. Try to reproduce on a directory and project without a hyphen in the name, just vnkswift for example.

Hope it helps

0
Cao Văn An On

In my case, I enable the test by go to show test navigator in Xcode, right-click and enable the test. And it worked.

1
Yogendra Singh On

If you are adding TestTarget after creating pod file. Then below code may be missing in the pod file.

target 'DemoTests' do
  inherit! :search_paths
  # Pods for testing
end

Add above code before end of target like below.

target 'Demo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for DocsDemo

  target 'DemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

end
0
SushiGrass Jacob On

Building off of @Cristik's answer, really it comes down to what is in your Sources folder. For me, I had an folder in Sources that needed to be exactly the same as what was in Tests except that what is in Tests needed to be suffixed with Tests. So, if you have Sources -> Foo-Bar, you had to have Tests -> Foo-BarTests. NO EXCEPTIONS.

One other note: all dashes will be converted to underscores so at the top of your test files, you'll have to add @testable import Foo_Bar.

2
Frank On

This seems to be caused my having a main.swift in your module directory. This results in an executable being build, instead of a library against which your test cases can be linked.

I resolved this issue by splitting my code into two modules. A library for which I have test cases and the application that only contains main.swift:

Package.swift
Sources\FooBarLib\
Sources\FooBarLib\Something.swift
Sources\FooBarLib\MoreStuff.swift
Sources\FooBarApp\main.swift
Tests\FooBarLibTests\TestSomething.swift

Then in my Package.swift make sure FooBarApp depends on FooBarLib:

import PackageDescription

let package = Package(
    name: "FooBar",
    targets: [
        .target(name: "FooBarLib"),
        .target(name: "FooBarApp", dependencies: ["FooBarLib"])
    ],
)

Then in TestSomething.swift you import the FooBarLib module:

@testable import FooBarLib
import XCTest

class TestSomething: XCTestCase {
    func testFunc() {
    }
}
0
Bill Chan On

In my case after migration a framework project to link other frameworks as XCFramework instead of universal frameworks, some tests can not be compiled in the linking step.

My fix is converting those failed objc tests to Swift and the issue is solved.

0
Wallace On

Make sure you are using a Unit Testing Bundle as your target.

Testing bundle options

For me, that was the problem.

Xcode used to ask, while creating a project, if we wanted to add Unit Testing first and a second option for UI Testing. But now, starting from Xcode 13, the only option is

Blockquote

which automatically will create UI Testing Bundles.

enter image description here

I got this solution from here.

You can differentiate by the icons:

Different targets created automatically and manually, respectively

0
Jianing On

In Swift 4, check if your .testTarget depends on FooBarLib.

    .testTarget(
        name: "FooBarLibTests",
        dependencies: ["FooBarLib"]),