Mocking Static Objects with Mockito in Spring Controller Test

Why?

There are times you may need to work on legacy code that has no test with it and has a lot of static objects. It could be adding new features, refactoring or fixing bugs, all which will require you to write some tests first before beginning any implementation in order to minimize regression.

If Powermock is not an option because it’s way too old at this point (by your standard, unmaintained, last check latest version was released Nov 2020), or for whatever other reason, then this article is something you could look into (Or just a way to inform :)).

Most stuff seem testable though you notice few classes are static objects. How you go about testing them with minimal effort?

Mockito 3.4.0 and Beyond

From 3.4.0, Mockito introduced feature which enables the mocking of static objects. For it to work, it requires the mock to be on the same thread as the static object. This would be an issue if you are writing spring boot controller test as the thread running the test and the thread taking in request will be different.

We can do a workaround by running the mocking mechanism within filter (we can extend OncePerRequestFilter and make it into a bean).

Filters

This filter will then be injected into the spring context whenever the test is running. This way, we can create a static mock that’s running within the same thread as the application under test.

try(MockedStatic<StaticMethod> mockedStaticMethod = mockStatic(StaticMethod.class, withSettings().verboseLogging())){
mockedStaticMethod.when(StaticMethod::returnStaticValue).thenReturn(getMockNonParamValue());
mockedStaticMethod.when(() -> StaticMethod.buildValue(any())).thenAnswer(answer -> getMockParamValue() + answer.getArgument(0));

filterChain.doFilter(request, response);
}

Also, dont forget to put the filterChain.doFilter call into the try.

But what if we want the mock to be a bit more dynamic? Changing the mock values according to the test scenarios you want to use?

Setting different mock value

From the filter bean created previously, we can make the properties we want changeable and readable via public methods. To ensure the updates and reads are correct, we can make the properties into atomic reference.

private AtomicReference<String> mockedNonParam = new AtomicReference<>(StringUtils.EMPTY);
private AtomicReference<String> mockedParam = new AtomicReference<>(StringUtils.EMPTY);

We can populate this value right before the test begin calling the endpoint of the controller for testing. Have a look at the full class below (TestStaticFilter):

Caveat

A potential problem with using this approach is if the stack of our service is using spring reactive. With Spring reactive, there will be multiple threads handling the requests. This will in fact render our tests broken as the mock wouldn’t be able to work properly.

Conclusion

Nifty tool if you are working on writing test codes with static classes to quickly achieve high coverage tests. Full code can be found at https://github.com/MazizEsa/testdemo

--

--

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
Maziz

Maziz

Someone who bricked a motherboard after flashing the wrong rom because of trying to OC an intel celeron from 400mhz to beyond 600mhz in 3rd grade.