Faster integration tests with spring boot — w/o parallelism

ALex Antonica
4 min readJun 13, 2022

--

Given

Long story short, in the startup that I’m currently involved we’re using the MonoEnv software development paradigm (check the link for more info), meaning that we only have one environment for the backend and that is production.

Every merge to main branch means a new deployment of the production app and as you might’ve guessed, that happens quite often, even 10 times a day if me and my colleagues have a PR merging spree.

When

So how can we be sure that what we’re merging is safe and doesn’t break anything? — yep, rhetorical question, TESTS of course, but not unit tests. We’re only relying on integration tests and that is because we think that at the end of the day, the endpoints should do what they were meant to do. This gets rid of the need of writing unit tests for every bit of logic and just tests that the services are working as expected. I agree with you that this makes bugs harder to pinpoint, but we accepted this tradeoff.

Our backend stack is Java with Springboot and a basic integration test would look like this

Simple integration test

Now, let’s add another service and create a second test.

In the second test MockTestApplication we’re injecting a different service. Let’s print out the spring application context and see what we get

As you can see the context is referencing the same object therefor the context is reused.

Nice and sweet, but this is not always the case. In real world we oftentimes need to mock our beans behaviour.

So what if we need to do just that 🤔

Let’s run again and see what we get

This time we get 2 application contexts. The use of @MockBean causes each ApplicationContext to have a different unique key under which it is stored in the context cache because the configuration changed.

In the first case, the application context had all the original beans added to it and if we were to apply a hash function on the context we would get the same value for both test classes because the context hasn’t changed.

But in the second case, one of the beans is getting replaced by a mock bean which by definition will change the result of the hash function, resulting in a cache miss when spring will try to load the context therefor it will reinitialise the whole context again.

Then

So how can we stop this? How can we make use of the same context? Because, imagine that we have hundreds if not thousands of integration tests and if it were to reinitialise the context for each of them…..web4.0 will already be vintage 😅

The solution is to have the same configuration for all the tests. That means that individual tests are not allowed to define their own @MockBean / @SpyBean. These should all be defined in a parent class.

Like so 👇 Also notice that the @SpringBootTest annotation was moved on the parent class so we don’t have to add it to each test. The Spring test framework will cache an ApplicationContext whenever possible between test runs.

Conclusion

We’ve seen how you can make integration tests run faster without using parallelism. Although there is no doubt that running them in parallel is the fastest method, but if your tests are written in such a way that running them in parallel will make you change almost all assertions or rewrite more than half of the existing tests, this is a gulp of fresh air until you and your team decide to actually do the changes for the tests to run in a || way.

--

--

ALex Antonica

Software engineer in my 5th year now, passionate and curious about new technologies, practices and ways of thinking. Never stop growing.