How to Use WireMock with JUnit 5 in Kotlin Spring Boot Application
Introduction
It is a common practice to use WireMock in our Integration Test to simulate a server response. It allows us to complete our testing, without having to call the actual web endpoint or start a web server, which is awesome!
As of the time of this writing, WireMock does not support JUnit 5. You can see the discussion here. It currently supports JUnit 4’s @Rule
and @ClassRule
to manage its lifecycle automatically.
So, how can we use WireMock in a more seamless way for our Test Classes that use JUnit 5 and have Spring manages its lifecycle?
What You Will Learn
I am going to walkthrough how you can create and register a WireMock Bean and have Spring Application Context manages its lifecycle, just like if you were to use JUnit 4 rules.
Pre-requisite
If you want a complete walkthrough on how to build an application in Kotlin that uses Spring Boot and Spring Webflux along with WireMock for its Integration Test (JUnit 4), check out this article.
Otherwise, you can just clone the GitHub repo here to see the final code. If you wan to follow through the tutorial and write the code yourselves, clone this commit.
WireMock in JUnit 4
This is how the Integration Test is currently written.
You can see that we use the following components in our test class:
@RunWith(SpringRunner::class)
: this provides the SpringApplicationContext
and allows Beans to be@Autowired
to your test instances.@ClassRule
: it runs thebefore()
method before the test cases start and theafter()
method after all the test cases finish. It is used to set up things that are used by all the test cases and it applies to static method.@JvmField
: this annotation tells the Kotlin compiler to expose the annotated Kotlin object as a field in Java. Essentially, it sets the annotated method to be a static method.
All of these are from JUnit 4 and they allow us to let the WireMock server to be managed by Spring.
Write a WireMock Bean for JUnit 5 Test
Now, moving on to JUnit 5, we are going to create a Context Initializer Class that extends ApplicationContextInitializer
and provides the WireMock
Bean that will be managed by Spring to our test classes. We will then pass it to the @ContextConfiguration
‘s initalizer
parameter.
ApplicationContextInitializer
can be used to initialise some configurable application context programmatically, for example, registering a Bean or Property sources. This means that for our WireMock Integration Test, we can programmatically register our WireMock Bean to the ApplicationContext
so then Spring will manage the lifecycle.
Alright, let’s go ahead and create a new file named WireMockContextInitializer.kt
. in our src/test/kotlin/io.codebrews.wiremockdemo
folder.
What we have done is that we overwrite the initialize()
method which gets called during the initialisation of the ApplicationContext
. We define a WireMockServer
instance with a random port and calls its start()
method, which will spin up a WireMock server.
We need to call the start()
method explicitly, because our WireMockServer
Bean is being registered outside of the Spring IOC container and as such, the default mechanism of callbacks will not be invoked.
Next, we need to register the WireMock Bean to the Spring IOC container so we can @Autowired
it like a normal Bean. We register the Bean to the Bean Factory as a Singleton object. Now, our WireMock Bean is available in the Spring IOC container.
applicationContext.beanFactory.registerSingleton(“wireMock”, wmServer)
Finally, we add an ApplicationListener
which listens to events related to the ApplicationContext
. As for our use case, we want the WireMock server to shutdown when our tests are finished running.
ContextClosedEvent
is the event that is raised upon the closing of the ApplicationContext
, which happens after our tests finish. So, this is why our ApplicationListener
is listening for that event and when it occurs, we call the stop()
method of WireMockServer
which will tear down the server.
Dynamically Overwrite Test Property Value
Last but not least, remember our application.properties
file, which contains the OpenWeather base url? It looks like this.
Recall that we set our WireMockServer
Bean to run on a random port. This means that our application will never hit the correct endpoint of the WireMock server. In other words, our tests will always fail. Now, this is a problem!
But, what if, somehow, we can dynamically overwrite the value of app.openweather.baseurl
at runtime based on the random port chosen by the WireMock server? Yes, that is possible. Let’s see how this can be done.
Isn’t this amazing? What’s going to happen is, Spring will set app.openweather.baseurl
to localhost with the port number coming from our WireMockServer
Bean. This means that our WireMock server will always run on an available port, so we can say goodbye to the Address already in use
exception. 💪
Migrate the Test Class to JUnit 5
Our custom Bean is ready and now, it’s time to implement it to our RouteTest
class, which is currently using JUnit 4. In this section, we will take a look at how to migrate to JUnit 5.
Add dependency to build.gradle.kts
To enable JUnit 5 testing, we need to add two things to our build.gradle.kts
:
org.junit.jupiter:junit-jupiter-api
package for test implementation. JUnit Jupiter is JUnit 5 andjunit-jupiter-api
package is where the annotations are contained, e.g.@Test
,@ExtendWith
,@BeforeAll
,@AfterAll
.useJUnitPlatform()
to tell Gradle to use the JUnit Platform, which enables JUnit 5 for our tests.
Along with adding dependency, we’ll also upgrade the versions of Kotlin and Spring Boot framework.
Go ahead and rebuild the project using the Gradle build tool.
Modify the RouteTest.kt
As mentioned in the earlier section, our current RouteTest.kt
class uses annotations that are from JUnit 4. We need to remove and replace them all with annotations from JUnit 5. We also need to modify some of the WireMock related code a little bit, specifically the stubFor
and verify
parts.
Alright, let’s go through some details now.
First, the @RunWith
annotation is removed because it is not available in JUnit 5 anymore. Because we have a Context Initialiser class that provides the WireMockServer
Bean, we are going to add Spring’s @ContextConfiguration
to register the WireMockServer
Bean before our test cases run.
Secondly, we removed the companion object
along with the @ClassRule
and @JvmField
annotations. Since the WireMockServer
is registered as a Spring Bean, we can just @Autowired
it.
Thirdly, I have added an @AfterEach
method to reset the WireMockServer
after each of the test cases in terms of its stub responses. Right now, it is actually not necessary as we only have one test case that really uses WireMockServer
. I only put it there so you know what to do when you have multiple test cases that rely on the WireMockServer
.
Lastly, we have to add the wireMockServer
object to our stubFor
and verify
methods or else we would get the following error.
org.apache.http.conn.HttpHostConnectException: Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)
Essentially, we need to add the specific instance of WireMockServer
that is actually running. Previously when using WireMockRule
, it actually registers the WireMock server instance so then, the stubFor
and verify
methods know which WireMock server is running and on which port. That’s why it was working fine.
Wrap Up
By the time you reach this section, you will have learned the following:
- using Spring’s
ApplicationContextInitializer<ConfigurableApplicationContext>
to register aWireMockServer
Bean to the Spring IOC during our Integration Test - migrating from JUnit 4 based test class to JUnit 5
I hope it helps and inspires you to explore more on WireMock and JUnit 5. 🙂