Search code examples
unit-testingkotlinkotlin-nativekotlin-multiplatform

Kotlin-multiplatform: How to execute iOS unit tests


I'm working on a Kotlin-multiplatform library for Android and iOS. I want to write some platform-specific unit test. The tests run as expected for the shared code and Android but not for iOS.

Below the build.gradle file of the shared code module.

apply plugin: "kotlin-multiplatform"

kotlin {
    targets {
        final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
                              ? presets.iosArm64 : presets.iosX64

        fromPreset(iOSTarget, 'iOS') {
            compilations.main.outputKinds('FRAMEWORK')
        }

        fromPreset(presets.jvm, 'android')
    }

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib-common"
        }
        commonTest.dependencies {
            implementation 'org.jetbrains.kotlin:kotlin-test'
            implementation 'org.jetbrains.kotlin:kotlin-test-junit'
        }
        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib"
        }
        androidTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test'
                implementation 'org.jetbrains.kotlin:kotlin-test-junit'
            }
        }
        iOSMain.dependencies {
        }
        iOSTest.dependencies {
            implementation 'org.jetbrains.kotlin:kotlin-test'
            implementation 'org.jetbrains.kotlin:kotlin-test-junit'
        }
    }
}

// workaround for https://youtrack.jetbrains.com/issue/KT-27170
configurations {
    compileClasspath
}

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'

    inputs.property "mode", mode
    dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode)

    from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile }
    into frameworkDir

    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

tasks.build.dependsOn packForXCode

and the structure of the SharedCode module is:

└── src
    ├── commonMain
    │   └── kotlin
    ├── commonTest
    │   └── kotlin
    ├── androidMain
    │   └── kotlin
    ├── androidTest
    │   └── kotlin
    ├── iOSMain
    │   └── kotlin
    └── iOSTest
        └── kotlin

The tests added in the androidTest and commonTest folders do run as expected but the ones added in the iOSTest do not run.

However, if I replace the the line fromPreset(iOSTarget, 'iOS') { compilations.main.outputKinds('FRAMEWORK') } for fromPreset(presets.macosX64, 'macos') and update the directory names accordly, the tests in the macosTest folder do run as expected.

Why it is not possible to run iOS test when building iOS frameworks? Any idea about what I'm doing wrong or how I can make this works? :)


Solution

  • Currently the kotlin-multiplatform plugin supports only running tests for host platforms (e.g. macOS or Windows). But you can manually add a task for executing iOS tests on a simualtor:

    task iosTest {
        def device = project.findProperty("iosDevice")?.toString() ?: "iPhone 8"
        dependsOn 'linkTestDebugExecutableIos'
        group = JavaBasePlugin.VERIFICATION_GROUP
        description = "Runs tests for target 'ios' on an iOS simulator"
    
        doLast {
            def binary = kotlin.targets.ios.binaries.getExecutable('test', 'DEBUG').outputFile
            exec {
                commandLine 'xcrun', 'simctl', 'spawn', device, binary.absolutePath
            }
        }
    }
    

    See the full build script here.