Search code examples
kotlinkotlin-multiplatform

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']);

Solution

  • 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.