Description
Hi!
I would like to start a discussion about the mechanism of caching application contexts that are using @MockBeans
across spring boot tests.
I was following this guide about testing the web layer: https://spring.io/guides/gs/testing-web/ and I was surprised that application context was build independently for every test class. I was hoping it will be cached to decrease test time, but I was wrong.
I've created small sample app to show this. Here is pull request: spring-attic/spring-boot-issues#56
There's one @Service
(FooBarService
) used by controller (FooBarApplication
) in two methods. And two tests that have some expectations about the web layer (as indicated by the @WebMvcTest
annotation). Those two tests work on the same context - the only difference is distinct mocks. But if you do mvn clean test
you'll see the spring banner twice, which means that context for the web layer is created two times.
I've done some research and debugging and found that this is caused by including the @MockBean
and @SpyBean
definitions in the key for context cache (through the MockitoContextCustomizer
that is a part of MergedContextConfiguration
).
Well... After rethinking all of this I can understand that from technical point of view those contexts are not the same, because mock@1 and mock@2 are not equal. But still I feel that my assumption that context should be reused is what users could expect. Especially if we realize that @WebMvcTest
is used to reduce the test time by not starting tomcat. Having this goal in mind it's hard to accept the extra time of repeated context building.
I'm not sure if such a change is a bug fixing or enhancement. I feel that this behaviour is bug but you can discuss it.
For now my workaround (or maybe it's official way to do this?) is to create an abstract test class with all @MockBeans
definitions, something like:
@RunWith(SpringRunner.class)
@WebMvcTest
public abstract class AbstractTest {
protected @MockBean FooBarService service;
}
public class FooTest extends AbstractTest {...}
But maybe it's better to consider an approach in which context is build once and mocks are replaced in beforeTestClass and afterTestClass methods of TestExecutionListener
? And to avoid refreshing dependencies in every class referencing those mocked beans maybe it's ok to generate some beanToMockProxy object, that will be autowired in controllers and will hold a reference to mockito mock (created per test class), proxing all the methods to that mock? I don't know... I'm just thinking loud.
Cheers,
Alek