Quantcast
Channel: all and sundry
Viewing all articles
Browse latest Browse all 250

Kotlin and JUnit 5 @BeforeAll

$
0
0

Introduction

In Kotlin, classes do not have static methods. A Java equivalent semantic can be provided using the concept of a companion object though. This post will go into details of what it takes to support a JUnit 5 @BeforeAll and @AfterAll annotation which depend on the presence of a 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, as the JUnit 5 platform can guarantee that these methods are exactly once before ALL tests. 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, an 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 @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)"

Correction
Per comments from the one and only Sébastien Deleuze, the previous test can be simplified by injecting in dependencies and properties via constructor injection, so the test can be re-written as:

@ExtendWith(SpringExtension::class)
@SpringBootTest
class BeforeAllSampleTest(@Value("\${some.key}") val 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
}

Reference

This stackoverflow answer was instrumental in my understanding of the nuances of JUnit 5 with Kotlin.

Viewing all articles
Browse latest Browse all 250

Trending Articles