Tyler Gannon
Tyler Gannon

Reputation: 1012

How to make my JS tests appear in Kotlin Multiplatform project

I'm using the IntelliJ IDEA Multiplatform project, and the jsTest Gradle task does not detect any tests. The jvmTest tests run no problem. When I run the jsTest task with debug output, I can see that the task runs and immediately finishes.

Gradle version 4.10.1. Kotlin version 1.3.0-eap.

How can I rectify the Gradle configuration, or what command can I run, so that the test will actually be detected, and (as written) fail?

build.gradle:

plugins {
    id 'kotlin-multiplatform' version '1.3.0-rc-131'
}
repositories {
    maven { url 'http://dl.bintray.com/kotlin/kotlin-eap' }
    mavenCentral()
}
kotlin {
    targets {
        fromPreset(presets.jvm, 'jvm')
        fromPreset(presets.js, 'js')
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
            }
        }
        commonTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-common'
                implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
            }
        }
        jvmMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
            }
        }
        jvmTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test'
                implementation 'org.jetbrains.kotlin:kotlin-test-junit'
            }
        }
        jsMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
            }
        }
        jsTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-js'
            }
        }
    }
}

test-project_test.js:

if (typeof kotlin === 'undefined') {
  throw new Error("Error loading module 'test-project_test'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'test-project_test'.");
}
if (typeof this['test-project'] === 'undefined') {
  throw new Error("Error loading module 'test-project_test'. Its dependency 'test-project' was not found. Please, check whether 'test-project' is loaded prior to 'test-project_test'.");
}
if (typeof this['kotlin-test'] === 'undefined') {
  throw new Error("Error loading module 'test-project_test'. Its dependency 'kotlin-test' was not found. Please, check whether 'kotlin-test' is loaded prior to 'test-project_test'.");
}
this['test-project_test'] = function (_, Kotlin, $module$test_project, $module$kotlin_test) {
  'use strict';
  var Sample = $module$test_project.sample.Sample;
  var assertTrue = $module$kotlin_test.kotlin.test.assertTrue_ifx8ge$;
  var Kind_CLASS = Kotlin.Kind.CLASS;
  var hello = $module$test_project.sample.hello;
  var contains = Kotlin.kotlin.text.contains_li3zpu$;
  var test = $module$kotlin_test.kotlin.test.test;
  var suite = $module$kotlin_test.kotlin.test.suite;
  function SampleTests() {
  }
  SampleTests.prototype.testMe = function () {
    assertTrue((new Sample()).checkMe() > 0);
  };
  SampleTests.$metadata$ = {
    kind: Kind_CLASS,
    simpleName: 'SampleTests',
    interfaces: []
  };
  function SampleTestsJS() {
  }
  SampleTestsJS.prototype.testHello = function () {
    assertTrue(contains(hello(), 'JSSDF'));
  };
  SampleTestsJS.$metadata$ = {
    kind: Kind_CLASS,
    simpleName: 'SampleTestsJS',
    interfaces: []
  };
  var package$sample = _.sample || (_.sample = {});
  package$sample.SampleTests = SampleTests;
  package$sample.SampleTestsJS = SampleTestsJS;
  suite('sample', false, function () {
    suite('SampleTests', false, function () {
      test('testMe', false, function () {
        return (new SampleTests()).testMe();
      });
    });
    suite('SampleTestsJS', false, function () {
      test('testHello', false, function () {
        return (new SampleTestsJS()).testHello();
      });
    });
  });
  Kotlin.defineModule('test-project_test', _);
  return _;
}(typeof this['test-project_test'] === 'undefined' ? {} : this['test-project_test'], kotlin, this['test-project'], this['kotlin-test']);

Upvotes: 4

Views: 1879

Answers (2)

Steven Jeuris
Steven Jeuris

Reputation: 19130

Inspired by kotlinx-io's multiplatform build configuration, without really knowing what I am doing, I managed to configure JavaScript tests to run using Mocha.

compileKotlinJs.configure {
    kotlinOptions {
        metaInfo = true
        sourceMap = true
        moduleKind = 'umd'
        main = "noCall"
        sourceMapEmbedSources = 'always'
    }
}

compileTestKotlinJs.configure {
    kotlinOptions {
        metaInfo = true
        sourceMap = true
        moduleKind = 'umd'
        main = "call"
        sourceMapEmbedSources = 'always'
    }
}

task copyJsDependencies(type: Copy, dependsOn: compileTestKotlinJs) {
    from compileKotlinJs.destinationDir
    into "${buildDir}/node_modules"

    def configuration = configurations.jsTestRuntimeClasspath
    from(files {
        configuration.collect { File file ->
            file.name.endsWith(".jar")
                ? zipTree(file.absolutePath).matching {
                    include '*.js'
                    include '*.js.map' }
                : files()
        }
    }.builtBy(configuration))
}

node {
    version = nodeVersion
    download = true
}

task installMocha(type: NpmTask) {
    args = ['install', 'mocha']
}

task runMocha(type: NodeTask, dependsOn: [installMocha, compileTestKotlinJs, copyJsDependencies]) {
    script = file('node_modules/mocha/bin/mocha')
    args = [compileTestKotlinJs.outputFile]
}

jsTest.dependsOn runMocha

Upvotes: 2

Ilya Idamkin
Ilya Idamkin

Reputation: 34

As stated by Kotlin Multiplatform Tutorial

At this point, test tasks for Kotlin/JS are created but do not run tests by default; they should be manually configured to run the tests with a JavaScript test framework.

You can use for example mocha framework to run the tests

Here is my setup to do so:

build.gradle:

plugins {
    id 'kotlin-multiplatform' version '1.3.10' //I'm using the released version of plugin,
                                               //but it seems that they have same API
    id 'com.moowork.node' version '1.2.0' //plugin for installing node
                                          //and running node and npm tasks
}
repositories {
    mavenCentral()
}
group 'com.example'
version '0.0.1'

apply plugin: 'maven-publish'

final kotlinRuntimeVersion = '1.3.10'

final nodeVersion = '11.2.0'
final nodeWorkingDir = project.buildDir
final nodeModules = "$nodeWorkingDir/node_modules"
final mochaVersion = '5.2.0'
final pathSeparator = System.properties["path.separator"]

kotlin {
    targets {
        fromPreset(presets.jvm, 'jvm')
        fromPreset(presets.js, 'js') {
            [compileKotlinJs, compileTestKotlinJs].each { configuration ->
                configuration.kotlinOptions {
                    moduleKind = 'umd'
                }
            }
        }
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
            }
        }
        commonTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-common'
                implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
            }
        }
        jvmMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
            }
        }
        jvmTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test'
                implementation 'org.jetbrains.kotlin:kotlin-test-junit'
            }
        }
        jsMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
            }
        }
        jsTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-js'
            }
        }
    }
}

//Workaround to copy kotlin libraries so they are visible during testing
def jsLibDir = "$compileKotlinJs.destinationDir/lib"
def jsTestLibDir = "$compileTestKotlinJs.destinationDir/lib"
configurations {
    jsLibs
    jsTestLibs
}
dependencies {
    jsLibs "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinRuntimeVersion"
    jsTestLibs "org.jetbrains.kotlin:kotlin-test-js:$kotlinRuntimeVersion"
}
task copyJsDependencies(type: Copy, dependsOn: compileKotlinJs) {
    configurations.jsLibs.each {
        from zipTree(it.absolutePath).matching { include '*.js'}
    }
    into jsLibDir
}
jsMainClasses.dependsOn copyJsDependencies
task copyJsTestDependencies(type: Copy) {
    configurations.jsTestLibs.each {
        from zipTree(it.absolutePath).matching { include '*.js'}
    }
    into jsTestLibDir
}
jsTestClasses.dependsOn copyJsTestDependencies

//Use mocha to run js tests
node {
    version = nodeVersion
    download = true
    workDir = file("$project.buildDir/nodejs")
    nodeModulesDir = file(nodeWorkingDir)
}
task installMocha(type: NpmTask, group: 'npm') {
    outputs.dir "$nodeModules/mocha"
    args = ['install', "mocha@$mochaVersion"]
}
task runMocha(type: NodeTask, dependsOn: [installMocha, jsMainClasses, jsTestClasses], group: 'npm') {
    environment = [ "NODE_PATH": "$jsLibDir$pathSeparator$jsTestLibDir$pathSeparator$compileKotlinJs.destinationDir" ]
    script = file("$nodeWorkingDir/node_modules/mocha/bin/mocha")
    args = [compileTestKotlinJs.outputFile]
}
jsTest.dependsOn runMocha

settings.gradle:

pluginManagement {
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "kotlin-multiplatform") {
                useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
            }
        }
    }
}
rootProject.name = 'test'

For some reasons it is important to disable metadata feature from gradle, for node plugin to work properly.

With this set up you will run js tests of jsTest gradle task, (which is important for example for CI) but they won't appear in the idea window as they do for java tests, and you still wouldn't be able to debug them.

To do so in IntelliJ IDEA you can create custom mocha run/debug configuration (Run | Edit Configurations from the main menu), and configure it similar to the runMocha gradle task.

Upvotes: 1

Related Questions