Kotlin 中 @BeforeAll 的正确解决方法是什么

What is proper workaround for @BeforeAll in Kotlin

目前 JUnit 5 API 只允许 @BeforeAll 在静态方法上。

所以如果我这样做,它不会编译:

@BeforeAll
  fun setup() {
    MockitoAnnotations.initMocks(this)
    mvc = MockMvcBuilders.standaloneSetup(controller).build()
}

为了在 Kotlin 中有一个静态方法,我必须像这样使用 companion object

companion object {
    @JvmStatic
    @BeforeAll
    fun setup() {
      MockitoAnnotations.initMocks(this)
      mvc = MockMvcBuilders.standaloneSetup(smsController).build()
    }
}

这将编译,但我无法访问父 class 的变量。那么用 Kotlin 调用 JUnit 5 @BeforeAll 的惯用方法是什么?

@BeforeAll 的文档所述:

Denotes that the annotated method should be executed before all @Test methods in the current class; analogous to JUnit 4’s @BeforeClass. Such methods must be static and are inherited.

以上内容对于 Kotlin 和 Java 都是正确的。请记住,默认情况下 Junit 将为每个测试用例创建一个单独的测试实例 class。 @BeforeAll 只适用于静态方法是有道理的,因为它应该在当前测试用例的任何代码之前被调用。静态方法 无法访问实例成员,因为它可以在没有实例的情况下被调用

如 Spring 文档所述:

The "standaloneSetup" on the other hand is a little closer to a unit test.

该示例表明您应该像这样使用实例成员:

class StandaloneTest {
  val smsController = ... // create instance of controller
  val MockMvcBuilders.standaloneSetup(smcController).build()
}

@BeforeAll 的用处有限,通常应避免使用,因为它可能会鼓励测试用例之间的运行时依赖性。

JUnit 5 有 @TestInstance(PER_CLASS) 注释可用于此目的。它启用的功能之一是 non-static BeforeAllAfterAll 方法:

@TestInstance(PER_CLASS)
class BeforeAllTests {

    lateinit var isInit = false

    @BeforeAll
    fun setup() {
        isInit = true
    }

   @TestFactory
   fun beforeAll() = listOf(
       should("initialize isInit in BeforeAll") {
           assertTrue(isInit)
       }
   )
}

fun should(name: String, test: () -> Unit) = DynamicTest.dynamicTest("should $name", test)

JUnit 5 支持测试扩展,例如BeforeAllCallback

import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.ExtensionContext

class BeforeAllTests : BeforeAllCallback {
    override fun beforeAll(context: ExtensionContext?) {
        System.out.println("Before all executed");
    }
}

在你的 Kotlin 测试中 class

@ExtendWith(BeforeAllTests::class)
class MyAppCoreTest : MyAppTest() {

    @Test
    fun someTest() { }
}

@SpringBootTest 中,这里有一个简单的解决方案。请注意,@BeforeAll 函数可以使用由 Spring.

注入的属性
@SpringBootTest(classes = [Application::class])
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {

    @Autowired
    lateinit var someBean: SomeBean

    lateinit var propertyForAll: PropertyForAll

    @BeforeAll
    fun setup() {
        propertyForAll = PropertyForAll(someBean.valueXyz())
    }

    @Test
    fun `First test`() {
        val something = propertyForAll.findSomething("Banana")
        // test continues
    }

    @Test
    fun `Second test`() {
        val something = propertyForAll.findSomething("Orange")
        // test continues
    }
}

值得一看:

您可以访问 companion 对象中的变量:

    companion object {

        private lateinit var objectToBeInitialized: Test

        @BeforeAll
        @JvmStatic
        fun setup() {
            objectToBeInitialized = Test()
        }
    }