Since we had a look at spring and springboot in a previous article. Let review how we can test the beast! Because like all good developers, you like writing good tested code with TDD aka Test Driven Development. Where you usually start with test… 😅

Test a simple Rest spring application

Simple Get endpoint

Let’s say you have a spring REST application in kotlin with a simple endpoint. That’s all good and well, but how do you test that in Kotlin. You can usually find a lot of information online, so let’s add this one to the mix:

@RestController
@RequestMapping("/v1")
class Endpoints {

  @GetMapping(value = ["/hello/{name}"], produces = ["application/json"])
  fun hello(@PathVariable(value = "name") name: String) = ResponseEntity(name), HttpStatus.OK)

}

This is a simple hello endpoint that will return the name parameter that is sent in the url. The @RequestMapping("/v1") annotation will prepend /v1 to all endpoints define in the class.

Bring in your test dependencies

The springboot dependencies will be automatically deduced by the plugin. That’s why, when importing springboot test packages, you may want to exclude the old junit v4 to start fresh with mockK and Junit5.

Here would be a simplified snippet of our build.gradle.kts for our test dependencies:

plugins {
    id("org.springframework.boot") version "2.2.7.RELEASE"
    id("io.spring.dependency-management") version "1.0.7.RELEASE"
    
}

dependencies {
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
    testImplementation("org.junit.jupiter:junit-jupiter:5.4.2")
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testImplementation("io.mockk:mockk:1.9.3")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(module = "junit")
        exclude(module="junit-vintage-engine")
        exclude(module = "mockito-core")
    }
}

Although mockito should still be compatible with Kotlin, the syntax gets weird because it’s conflicting with some Kotlin keywords like when(). So usually when using Kotlin, you’ll go with mockK instead.

And to avoid interference, we exclude Mockito from spring-boot-starter-test as well.

Spring application test class

Let’s create our ApplicationTest class to test our springboot REST application. You would find some annotation to resolve the bean in the context load, specify some information and properties.

@ExtendWith(SpringExtension::class)
@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    classes = [ApplicationTest.ControllerTestConfig::class],
    properties = ["spring.example.property=foobar"]
)
@ActiveProfiles(value = ["test"])
internal class ApplicationTest {

  var testRestTemplate = TestRestTemplate()

  @LocalServerPort
  var serverPort: Int = 0

  @TestConfiguration
  internal class ControllerTestConfig {
    @Bean
    fun foo(): Foo = mockk()
  }
  
  private fun applicationUrl() = "http://localhost:$applicationPort"
}   

You can see other annotations:

  • @SpringBootTest: Because it’s a springboot test, you can pass spring properties in properties and define some variables.
  • @ActiveProfiles(value = ["test"]): To use a different profile when spinning the spring application (you might not want a certain bean to be built for the test, or you have a test profile defined with different values.)
  • @LocalServerPort: In this case will represent the port on which your web springboot app will be hosted.
  • @TestConfiguration: This is to define the test configuration, you can mock or update beans there.

The ControllerTestConfig is here if you have some configuration you want to mock or modify for the test. For example, here we are mocking the bean foo meaning that any autowire of that bean will take this mock version.

Write your first test

I came across Kotlin Unit test best practice that gives a lot of cool tips on how to test your kotlin code.

Here is a simple test to send a GET request to your application using the testRestTemplate. A springboot test tool to make REST request, so that you can test the behaviour of your application. You can check and assert the result to make sure everything is alright.

@Test
fun simpleGetTest() {
  val result = testRestTemplate.exchange(
      URI(applicationUrl() + "/v1/hello/world"),
      HttpMethod.GET,
      HttpEntity(""),
      String::class.java)

  Assertions.assertEquals(HttpStatus.OK, result.statusCode)
  Assertions.assertEquals("world", result.body)
}

You can see the result is in a String::class.java to get the result body as a String. Be careful, if you set the result body type to Void::class.java you won’t get the body at all (even if there’s one).

Mock your beans

Obviously here you’re testing end to end your application, and in some cases it may connect to another part of the system. In order to simplify the testing, you can mock external dependencies for your test to run smoothly.

If there are some bits of logic from a spring component that you want to test, you can still “autowire” them in another test file! Thus, you can test different niche behaviour or custom error handling.

Using mockK only

Here is how you would do that with our @Bean foo from the previous example. You would set a special test profile here and use for the real bean "!test" to avoid interferences. Be sure if you have a missing bean error to add your test configurations class in classes in your @SpringBootTest

@Bean
@Profile(value = ["test"])
fun foo(): Foo {
  val fooClient: Foo = mockk(relaxed = true)
  every { fooClient.do(any()) } just Runs
  every { fooClient.isOpen() } returns true
  every { fooClient.close() } throws RuntimeException()
  return fooCLient
}

So here you can see three behaviours that were defined for our Beans. We define the behaviour of some method, define a return value or throw an exception. This way we can test some custom behaviour.

With springmockk

However if you prefer a syntax closer to Mockito, you can use springMockk with mockK by adding this dependency from your gradle file:

testImplementation("com.ninja-squad:springmockk:2.0.2")

Then you can just define your mocked bean like:

@MockkBean
lateinit var foo: Foo

And then you can use “ every { … } + behaviour” the mockK way to define the behaviour of the mockkBean in each test. Make sure you have Mocktio excluded for this one, as it’s strongly recommended.