Geek Culture
Published in

Geek Culture

Spring TestConfigurations with Kotlin

This week, I had to add Elasticsearch to my project. Since the client for Elasticsearch needs a running server to establish a connection and make requests, my project now has a dependency to other infrastructure. This was not a really big problem, because I could just mock the client for the tests that are using the client.

A little later, I added logic to initialize the Elasticsearch index on startup of my application. This was when the problem became obvious. Since this startup function was calling functions of the client, I would have needed to mock the client for every test, which can work for a few tests, but gets really messy when you have a few hundreds. 😅. Because of that reason, I started using Spring TestConfigurations.

What is a TestConfiguration?

According to Reflectoring.io:

The @TestConfiguration annotation is a useful aid for writing unit tests of components in a Spring Boot application. It allows us to define additional beans or override existing beans in the Spring application context to add specialized configurations for testing.

Specifically for our use case, we can overwrite an existing bean for the tests that don’t actually use the real implementation. We can define a mock and its behaviour once, and can use this configuration on every test class that we want.

Creating a TestConfiguration

First of all, we need to create a new file to place our configuration in.

Here we annotate the configuration class with @TestConfiguration to exclude it from Component-Scanning and letting Spring know that we want to define a new configuration.

After that, we define a new elasticClient bean which is a standard Kotlin function that returns the mock that we defined in it. The only special thing is, that you have to annotate it with @Bean . Note that the name of this function needs to be the same as the name of the bean that you want to inject from another class.

How to use the Configuration

First open the test, that you want this configuration to apply. This test might look something like this:

As you can see, an ElasticSearchAdapter gets mocked, which in its original implementation would have a save method, which calls methods of the elasticClient that relies on external infrastructure.

The minimal implementation of the StandardDocumentationRepository looks like this:

Since the onStartup() function runs even before the BeforeEach() function, we still use the original beans instead of our mocks. This naturally leads to failing tests.

The solution to this problem is really simple. We use our configuration, that we created above and apply it to every test. A minimal example looks like this:

The only thing we added is the @Import annotation. This makes sure that our configuration gets loaded and the bean gets overwritten even before the onStartupEvent function.

One last thing to do is to add the following line to your src/test/resources/application.properties file.

Please take notice that it’s really important to put it in the test properties and not into the main properties, since we don’t want to override beans in the main context.

spring.main.allow-bean-definition-overriding=true

This makes sure, that we can overwrite the existing bean with our mocked bean.

This leads us to having green tests again, since the mock in our test can now overwrite the configuration with its own specific behaviour.

Reflection

What went good

There were many great resources available for Java, so I understood the concept quite easily. The actual implementation of this configuration was also pretty simple.

What needs improvement

What really took time, was to understand why the tests where failing in first place. First I thought that it might be because I had two “ApplicationReadyEvent” functions in my project, but this is allowed in Spring. After reading the logs of Spring, it became obvious, that it had something to do with the ElasticClient. Especially with its connection to the server (which wasn’t running… obviously). Next time I would read the logs first, and not only the first part of the exception.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store