Kotlin and JUnit 5 @BeforeAll
Enjoying JUnit5's features but wish you could translate them easily to Kotlin? Here's a guide to the @BeforeAll and @AfterAll annotations.
Join the DZone community and get the full member experience.
Join For FreeIn Kotlin, classes do not have static methods. A Java equivalent semantic can be provided to callers using the concept of a companion object, though. This post will go into detail of what it takes to support JUnit 5 @BeforeAll and @AfterAll annotations, which depend on the presence of static methods in test classes.
BeforeAll and AfterAll in Java
Junit 5 @BeforeAll-annotated methods are executed before all tests, and @AfterAll is exected after all tests. These annotations are expected to be applied to static methods:
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Junit5BeforeAllTest {
private static final Logger LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest.class);
@BeforeAll
static void beforeAll() {
LOGGER.info("beforeAll called");
}
@Test
public void aTest1() {
LOGGER.info("aTest1 called");
LOGGER.info(this.toString());
}
@Test
public void aTest2() {
LOGGER.info("aTest2 called");
LOGGER.info(this.toString());
}
@AfterAll
static void afterAll() {
LOGGER.info("afterAll called");
}
}
A rough flow is: The JUnit platform calls the "@BeforeAll" annotated methods, then for each test, it creates an instance of the test class and invokes the test. After all, tests are executed, the "@AfterAll" annotated static methods are called.
This is borne out by the logs. See how the instance ids(from toString() of Object) is different:
2018-03-28 17:22:03.618 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : beforeAll called
2018-03-28 17:22:03.652 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : aTest1 called
2018-03-28 17:22:03.653 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : com.pivotalservices.cookbook.Junit5BeforeAllTest@7bc1a03d
2018-03-28 17:22:03.663 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : aTest2 called
2018-03-28 17:22:03.664 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : com.pivotalservices.cookbook.Junit5BeforeAllTest@6591f517
2018-03-28 17:22:03.669 INFO --- [ main] c.p.cookbook.Junit5BeforeAllTest : afterAll called
This default lifecycle of a JUnit 5 test can be changed by an annotation, though, if the test class is annotated the following way:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Junit5BeforeAllTest {
....
}
The advantage now is that the @BeforeAll and @AfterAll annotations can be placed on non-static methods. The catch though is that any instance-level state will not be reset before each test.
BeforeAll and AfterAll in Kotlin
So how does this translate to Kotlin?For the default case of a new test instance per test, equivalent Kotlin test code looks like this:
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.slf4j.LoggerFactory
class Junit5BeforeAllKotlinTest {
@Test
fun aTest1() {
LOGGER.info("aTest1 called")
LOGGER.info(this.toString())
}
@Test
fun aTest2() {
LOGGER.info("aTest2 called")
LOGGER.info(this.toString())
}
companion object {
private val LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest::class.java)
@BeforeAll
@JvmStatic
internal fun beforeAll() {
LOGGER.info("beforeAll called")
}
@AfterAll
@JvmStatic
internal fun afterAll() {
LOGGER.info("afterAll called")
}
}
}
A Kotlin companion object with methods annotated with @JvmStatic does the job.
Simpler is the case where the lifecycle is modified:
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.slf4j.LoggerFactory
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class Junit5BeforeAllKotlinTest {
private val LOGGER = LoggerFactory.getLogger(Junit5BeforeAllTest::class.java)
@BeforeAll
internal fun beforeAll() {
LOGGER.info("beforeAll called")
}
@Test
fun aTest1() {
LOGGER.info("aTest1 called")
LOGGER.info(this.toString())
}
@Test
fun aTest2() {
LOGGER.info("aTest2 called")
LOGGER.info(this.toString())
}
@AfterAll
internal fun afterAll() {
LOGGER.info("afterAll called")
}
}
My personal preference is for the companion object approach, as I like the idea of a deterministic state of the test instance before the test method is executed. Another advantage of the approach is with Spring Boot-based tests, where you want Spring to act on the test instance (inject dependencies, resolve properties, etc.) only after a @BeforeAll-annotated method is called. To make this more concrete, consider the following example:
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Configuration
import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringExtension::class)
@SpringBootTest
class BeforeAllSampleTest {
@Value("\${some.key}")
private lateinit var someKey: String
companion object {
@BeforeAll
@JvmStatic
fun beforeClass() {
System.setProperty("some.key", "some-value")
}
@AfterAll
@JvmStatic
fun afterClass() {
System.clearProperty("some.key")
}
}
@Test
fun testValidateProperties() {
assertThat(someKey).isEqualTo("some-value")
}
@Configuration
class SpringConfig
}
This kind of a test will not work at all if the lifecycle were changed to "@TestInstance(TestInstance.Lifecycle.PER_CLASS)".
Reference
This StackOverflow answer was instrumental in my understanding of the nuances of JUnit 5 with Kotlin.Published at DZone with permission of Biju Kunjummen, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments