Kotlin JS 中的单元测试

Unit testing in Kotlin JS

我在 Kotlin JavaScript 中有一个使用 React 的简单项目。我已经添加了单元测试,但是当我 运行 它们时,它们似乎调用了生产代码的主要方法,并且在尝试达到不存在的 DOM 结构时初始化失败。尽管经过测试的 class 没有以任何方式引用 React 或 DOM。

当来自 Intelij IDEA 的 运行 或 gradlew build 时,错误看起来是一样的(为了清楚起见,我用 /APP/ 替换了我的项目的完整路径):

Testing started at 17:51 ...
> Task :cleanBrowserTest
> Task :packageJson UP-TO-DATE
> Task :testPackageJson UP-TO-DATE
> Task :kotlinNodeJsSetup SKIPPED
> Task :kotlinNpmInstall
> Task :compileKotlinJs
> Task :processResources
> Task :mainClasses
> Task :compileTestKotlinJs
> Task :testProcessResources NO-SOURCE
> Task :testClasses

> Task :browserTest

(...)

Error: Target container is not a DOM element.

    at render (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:25359:13)

    at render_0 (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:29868:5)

    at main (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146311:5)

    at Object.<anonymous> (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146315:3)

    at http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146289:37

    at Object.../example/kotlin/example.js (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146292:2)

    at __webpack_require__ (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:20:30)

    at http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146346:134

    at Object../kotlin/example-test.js (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:146351:2)

    at __webpack_require__ (http://localhost:9876/absoluteD:/APP/build/js/packages/example-test/adapter-browser.js?92ccabfdcfa982960828b65b2f4e2683080859b4:20:30)

HeadlessChrome 81.0.4044 (Windows 10.0.0) ERROR

  Uncaught Error: Target container is not a DOM element.

  at d:/APP/build/js/node_modules/react-dom/cjs/react-dom.development.js:24828:1 <- D:/APP/build/js/packages/example-test/adapter-browser.js:25359:7

(...)


> Task :browserTest FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':browserTest'.
> command 'C:\Users\Arsen\.gradle\nodejs\node-v12.14.0-win-x64\node.exe' exited with errors (exit code: 1)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 10s
8 actionable tasks: 6 executed, 2 up-to-date

最小示例:

./build.gradle.kts

plugins {
    id("org.jetbrains.kotlin.js") version "1.3.70-eap-184"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
    maven("https://kotlin.bintray.com/kotlin-js-wrappers/")
    mavenCentral()
    jcenter()
}

dependencies {
    implementation(kotlin("stdlib-js"))

    implementation("org.jetbrains:kotlin-react:16.13.0-pre.94-kotlin-1.3.70")
    implementation("org.jetbrains:kotlin-react-dom:16.13.0-pre.94-kotlin-1.3.70")
    implementation(npm("react", "16.13.1"))
    implementation(npm("react-dom", "16.13.1"))
    testImplementation(kotlin("test-js"))
}

kotlin.target.browser {
}

./settings.gradle.kts

pluginManagement {
    repositories {
        maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }

        mavenCentral()

        maven { setUrl("https://plugins.gradle.org/m2/") }
    }
}
rootProject.name = "example"

./src/main/resources/index.html

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <div id="root"></div>
        <script src="example.js"></script>
    </body>
</html>

./src/main/kotlin/Main.kt

import react.dom.*
import kotlin.browser.document

fun main(args: Array<String>) {
    render(document.getElementById("root")) {

    }
}

./src/main/kotlin/DummyClass.kt

class DummyClass {
    fun foo(): String {
        return "foo"
    }
}

./src/test/kotlin/ExampleTest.kt

import kotlin.test.*

class ExampleTest {
    @Test
    fun foo() {
        assertEquals(DummyClass().foo(), "foo")
    }
}

如果我根本不引用生产代码(从测试中删除 DummyClass().foo()),或者当 main 方法不调用 render(document.getElementById("root")).

时,就不会发生错误

PS:如果重要的话我 运行 Windows

上的代码

这不是正确的答案 - 但可以立即解除阻止。

回答指出

the test environment doesn't supply the DOM with an app id.

我们的测试框架也是如此——它没有提供 ID 为 'root' 的元素。 这让我相信 main/resources/index.html 没有被测试框架使用。异常跟踪确认 - 注意它是如何从 __webpack_require__

开始的

理想的解决方案是让 karma 或 webpack 为我们的代码提供正确的 index.html;但我不知道该怎么做。

在此期间,您可以

  • 删除 <div id="root"></div> 作为 index.html 的一部分
  • 让您的 kotlin 代码生成它,如下所示:

    ./src/main/kotlin/Main.kt

fun main(args: Array<String>) {
    document.body!!.insertAdjacentHTML("afterbegin", "<div id='root'></div>" )
    render(document.getElementById("root")) {

    }
}

希望对您有所帮助

我遇到了同样的问题,我已经解决了。多亏了@Yogesh Nachnani 的解决方法,我才明白缺少了什么。事实上,所有的资源文件都不包括在内。因此,首先我使用 fs-extra 创建了一个新的解决方法(将文件从资源复制到本地目录),然后在探索 Karma 时我发现最干净的解决方案是使用代理将请求重定向到资源。因此,我将以下代码用于 karma.config.js:

const path = require('path');
const resourcesSourcePath = path.resolve(__dirname, '../../../../build/processedResources/js/main');
const setupFile = path.resolve(__dirname, '../../../../src/test/setup.js');
config.files.unshift(setupFile);
config.proxies = {
    "/strings/": "absolute" + resourcesSourcePath + "/strings/",
    "/css/": "absolute" + resourcesSourcePath + "/css/",
    "/images/": "absolute" + resourcesSourcePath + "/images/"
}

这样,如果您有很多资源,就不必等待内容的副本。但是,我没有找到在 index.html 上启动业力的方法,但由于我现在可以访问所有资源,所以我只是使用了@Yogesh Nachnani 提出的解决方法,因为 HTML 文件通常不会对测试有很大影响(至少对我而言)。因此,我添加了一个 setup.js 文件,其中仅包含:

document.body.insertAdjacentHTML('afterbegin', "<div id='root'></div>");

当您使用 render(document.getElementById("root")){} for React 时,它可以防止呈现目标错误。