Testing Quarkus with Kotlin, JUnit and MockK

The rather young framework Quarkus becomes more and more a serious competitor to established microservice frameworks such as Spring Boot. This is largely due to the fact that the powerful but yet lightweight application framework offers small memory footprint and reduced boot time. And it is tailored for the JVM as well as Oracle’s GraalVM. Hence it’s not only interesting for developing microservices in containerized cloud platforms such as Kubernetes, but also for serverless computing.
When developing applications with Quarkus you will eventually have to write (unit) tests to determine whether your code behaves as expected. In this post I will give you an insight how to use JUnit and MockK to create isolated Quarkus tests with mocked CDI beans.
Table of contents
TLDR: How to mock your Quarkus CDI beans with JUnit and MockK
You are already familiar with Kotlin, Quarkus, JUnit and MockK and just need a practical code example? Here you go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
val mainService: MainService = mockk() @QuarkusTest @TestInstance(PER_CLASS) class RestControllerTests { @BeforeAll fun setupMocks() { QuarkusMock.installMockForType(mainService, MainService::class.java) } @Test fun `GET - hello (with mocked main service)`() { every { mainService.sayHello() } returns "Hello from MOCKED main service" val expectedJson = """{ "greeting": "Hello from MOCKED main service" }""" given() .`when`()["/hello"] .then() .statusCode(200) .contentType(MediaType.APPLICATION_JSON) .body(JsonMatcher.jsonEqualTo(expectedJson)) } } |
The example above, which can be found on GitHub, shows how within the RestControllerTests class the CDI bean MainService is mocked and a specific behaviour is defined.
If, however, you are interested in some more details and further Kotlin implementations, then please continue reading.
Quarkus and Kotlin
In the last few years Kotlin could establish itself as a popular alternative to Java. Although it had its first breakthrough in Android development, it’s now widely used in the JVM backend world. This is reflected as almost all relevant frameworks support Kotlin as a first-class language (e.g. Spring, Micronaut, Vert.x, Javalin, http4k, Ktor, etc.). And so does Quarkus! See this blog post for a first introduction.
So if you have the possibility to choose between Java and Kotlin as a backend developer – I would always recommend picking Kotlin! The reasons for this are – besides the more expressive and concise syntax – the numerous modern language features it provides, e.g. Null Safety, Data Classes, Multiline String Literals, String Templates, Extension Functions and Coroutines.
Why should you use JUnit and MockK to write tests?
Alrighty then, you have decided to create an application with Quarkus and Kotlin. Great choice!
Sooner or later (hopefully sooner 😉 ) you need to take care of writing tests. And you choose a test framework for that. JUnit is pretty common in the JVM world and I have used it successfully in several contexts with different application frameworks. And at least since JUnit 5 was released I’m really pleased to use it with its excellent Kotlin support.
When writing tests at some point you commonly have to mock parts of your application. E.g. if you test a RestController you might want to mock the underlying Service.
As a Java developer you are probably familiar with Mockito, a well established and powerful tool to create mocks in unit tests. Although there are enhancements to use Mockito with Kotlin it was primarily built for Java and therefore has certain limitations. If you want to use the full potential of Kotlin for mocking, MockK is the better option. It was designed from scratch for Kotlin and therefore doesn’t need some “hacks” that are necessary in Mockito for Kotlin. For instance, mocking a final class (all classes in Kotlin are by default final) is easier with MockK.
Mocking Quarkus CDI beans with MockK
But enough opening words – let’s have a look at the implementation. Imagine a very basic application like this:
There’s a RestController which handles HTTP calls and delegates to an underlying MainService. The MainService itself has a dependency to a SubService.
Note: The whole example can be found on GitHub, where you can also easily check which Gradle/Maven dependencies are needed.
Mocking a Service call
First of all let’s have a closer look at the MainService:
1 2 3 4 5 6 |
@ApplicationScoped class MainService( private val subService: SubService ) { fun sayHello() = "Hello from the REAL MainService - ${subService.sayHello()}" } |
And this is the Kotlin implementation of the SubService:
1 2 3 4 |
@ApplicationScoped class SubService { fun sayHello() = "Hello from the REAL SubService" } |
In line 3 the SubService CDI bean is injected into the MainService via Constructor Injection. With calling the MainService’s function sayHello() the implementation returns “Hello from the REAL MainService – Hello from the REAL SubService”.
It’s very important to define the services as normal scoped CDI beans – the most common of which are @ApplicationScoped and @RequestScoped. Beans annotated with @Singleton or @Dependent scope cannot be mocked with QuarkusMock which will be used later on. For details please refer to the Quarkus documentation.
To test the MainService a JUnit test is created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MainServiceUnitTests { private val subService: SubService = mockk() private val testee = MainService(subService) @Test fun `call sayHello() with mocked sub service`() { every { subService.sayHello() } returns "Hello from MOCKED SubService" val result = testee.sayHello() assertThat(result).isEqualTo("Hello from the REAL MainService - Hello from MOCKED SubService") } } |
This is a very basic unit test where the “testee“ – namely the MainService – is simply instantiated (line 5). And the mocked SubService, which was declared in line 3, is passed as a parameter.
The test itself (line 7-14) follows the AAA pattern: Arrange-Act-Assert
In the first step the mock’s behaviour is arranged. One of the big advantages of MockK is its easy readable DSL: You can clearly see that line 9 defines that every call of the mocked SubService function sayHello() should return “Hello from MOCKED SubService”.
In the next step – the Act section – the actual method call is executed. And finally the result is verified by an AssertJ assertion.
For this basic example such a plain unit test without any “Quarkus framework magic” is absolutely sufficient. Job done!
But what if there are framework features or annotations within your service which should be validated by the test as well? For example, we had the situation where a service function needed to be cached and therefore was marked with Quarkus’ own @CacheResult. And a test should validate that caching works correctly. In this case a plain unit test is no longer adequate – you need Quarkus to be started for your test execution.
This means you need some kind of test extension that starts Quarkus and keeps it running for the duration of the test run. Luckily with quarkus-junit5 comes @QuarkusTest – which does exactly that.
In line 3 of the following – let’s call it “Int-Test” – you can see how this annotation is applied:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
val subService: SubService = mockk() @QuarkusTest @TestInstance(PER_CLASS) class MainServiceIntTests { @BeforeAll fun setupMocks() { QuarkusMock.installMockForType(subService, SubService::class.java) } @Inject @field: Default lateinit var testee: MainService @Test fun `call sayHello() with mocked sub service`() { every { subService.sayHello() } returns "Hello from MOCKED SubService" val result = testee.sayHello() assertThat(result).isEqualTo("Hello from the REAL MainService - Hello from MOCKED SubService") } } |
The declaration @TestInstance(PER_CLASS) configures JUnit to create only one instance of the test class and reuse it between tests. The default would be PER_METHOD, but that requires the setupMocks() function in line 8-10 to be static as it is annotated with @BeforeAll. And static behaviour in Kotlin is handled differently than in Java.
In chapter “Other possible Kotlin implementations” I will show different ways to specify this JUnit behaviour with Kotlin, e.g. with a companion object or by utilizing @BeforeEach. Please have a look at them, too, perhaps they fit better to your personal coding style.
In line 1 MockK is used to create a mock for the SubService. Within the setupMocks() function the utility class QuarkusMock applies this mock. More precisely, the method installMockForType() installs the mock for the specified CDI bean.
Note: The “old approach” to create mocks in Quarkus tests by adding the annotation @Mocks leads to several problems. The most significant is that such a mock defined in one test class influences the behaviour of other test classes, which breaks the golden rule of testing: tests must be independent of each other. Therefore using QuarkusMock is the recommended approach.
The MainService as “testee” is declared in line 12-14. The @field: Default is needed to make CDI’s @Inject work with Kotlin as it has no @Target on the annotation definition (see https://quarkus.io/guides/kotlin for details).
The actual test in line 16-23 is similar to the one in the previous basic unit test.
Mocking Beans in a RestController
So far, so good. By mocking the SubService we could test our MainService isolated from its dependencies. But what about the RestController?
As as start let’s have a look at the implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Path("/") class RestController( private val mainService: MainService ) { @GET @Path("/hello") @Produces(APPLICATION_JSON) fun greeting(): Response = mainService.sayHello().let { Response.ok(GreetingResponse(it)).build() } } data class GreetingResponse(val greeting: String) |
The MainService bean is injected in the class’s constructor. The function in 6 -11 defines that when calling the endpoint /hello with the HTTP verb GET this response is returned:
1 2 3 |
{ "greeting": "Hello from the REAL MainService - Hello from the REAL SubService" } |
To test your RestController isolated from the underlying MainService a test class like this is required:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
val mainService: MainService = mockk() @QuarkusTest @TestInstance(PER_CLASS) class RestControllerTests { @BeforeAll fun setupMocks() { QuarkusMock.installMockForType(mainService, MainService::class.java) } @Test fun `GET - hello (with mocked main service)`() { every { mainService.sayHello() } returns "Hello from MOCKED main service" val expectedJson = """{ "greeting": "Hello from MOCKED main service" }""" given() .`when`()["/hello"] .then() .statusCode(200) .contentType(MediaType.APPLICATION_JSON) .body(JsonMatcher.jsonEqualTo(expectedJson)) } @Test fun `GET - exception is mapped to status 500`() { every { mainService.sayHello() } throws RuntimeException() given() .`when`()["/hello"] .then() .statusCode(500) } } |
Defining and applying the mock is equivalent to what was described in the previous chapter (line 1 and line 8-10).
This is followed by two tests:
The first test (line 12-25) validates that a (mocked) result from the MainService is correctly serialized to JSON. To call the /hello endpoint and validate the outcome RestAssured is used.
The second test in line 28-35 checks that if the mocked MainService throws an exception the RestController returns the status code 500 (Internal Server Error).
To be honest – testing if Quarkus maps a RuntimeException to the status code 500 is obviously pretty useless. This example is vastly simplified. However you could want to test if user defined exceptions are mapped to certain status codes, e.g. something like a UserNotFoundException to status 404 or an OptimisticLockException to status 409.
Other possible Kotlin implementations
As mentioned previously the usage of @TestInstance(PER_CLASS) is not the only way to specify JUnit’s behaviour. I personally like the short and concise way it offers to declare and apply the CDI bean mocks. Nevertheless there are (at least) two more options to be mentioned:
Using a companion object
As the main problem is that @BeforeAll in combination with @TestInstance(PER_METHOD) needs a static method you can move the function setupMocks() to a companion object and annotate it with @JvmStatic.
1 2 3 4 5 6 7 8 9 |
companion object { val subService: SubService = mockk() @BeforeAll @JvmStatic fun setupMocks() { QuarkusMock.installMockForType(subService, SubService::class.java) } } |
In addition this allows you to declare your mock within the same companion object, so mock declaration and application are in the same scope of your code.
Using @BeforeEach
Of course you can use @BeforeEach instead of @BeforeAll as this doesn’t require a static method:
1 2 3 4 5 6 |
private val subService: SubService = mockk() @BeforeEach fun setupMocks() { QuarkusMock.installMockForType(subService, SubService::class.java) } |
This means that the mock is (re-)declared and applied before each test. Therefore if you have many mocks and even more tests this might somewhat impact your test execution time.
Unsolved issues
So far we have discovered two points you should be aware of:
Mocking RestClients
If you try to mock a bean that is declared as @RegisterRestClient the mocking fails.
This means when you inject such a bean with @RestClient in a service and try to install a mock in the corresponding test class you get an exception like this when executing the test:
1 2 |
No bean found for required type [interface info.novatec.RestClient] and qualifiers [[]] javax.enterprise.inject.UnsatisfiedResolutionException: No bean found for required type [interface info.novatec.RestClient] and qualifiers [[]] |
A colleague of mine has opened an issue for this and it looks as if the Quarkus team is already on it.
Using @Nested Inner Classes
When using Kotlin and JUnit it is a common practice to structure your test classes with @Nested Inner Classes. For example you could structure your RestController test like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@QuarkusTest class RestControllerTests { @Nested inner class GetUser { @Test fun `by userId`() { // TBD. } @Test fun `by name`() { // TBD. } } @Nested inner class SaveUser { @Test fun `for new user`() { // TBD. } @Test fun `for existing user`() { // TBD. } } } |
Unfortunately this structuring doesn’t work in tests that are annotated with @QuarkusTest. Although the usage of JUnit’s @Nested in Java seem to work for Quarkus, the combination with Kotlin’s Inner Classes still causes difficulties.
Conclusion
Testing is a crucial part of application development, no matter what framework, language or library you use. Personally I am a big fan of Kotlin. And Quarkus offers – beneath its Kotlin support – a “best breed of libraries and standards”. Thus I think it is an excellent choice. Together with JUnit and MockK, efficient testing is not only possible but also fun.
What are your experiences with Quarkus, Kotlin, JUnit and MockK? Or what is your answer to the ultimate question of testing, the universe, and everything. Leave a comment, I will try to get back to you.
Comment article
Recent posts


Comments
Merli Thomas
Thank you so much for this article.
Not only is it a great introduction to testing of Quarkus applications, but the comment that mocking rest clients doesn’t work out-of-the-box finally helped me resolving an issue that had bugged me (on and off) for weeks.
Schwörer Christian
Thanks for your nice feedback, Thomas. I really appreciate that. 🙂
Good to know that the article was helpful. As mentioned we used the Quarkus MockK extension to mock our Rest clients: https://github.com/quarkiverse/quarkus-mockk
Regards,
Christian
Schwörer Christian
Btw.: There is a very promising Quarkus MockK extension: https://github.com/quarkiverse/quarkus-mockk
I will add it to the blog post as soon as I find the time to do so. My GitHub repo contains already corresponding code examples:
https://github.com/csh0711/quarkus-kotlin-mockk/blob/master/src/test/kotlin/info/novatec/services/MainServiceIntTestsWithMockKExtension.kt