Skip to content

Context not being reused in tests when MockBeans are used #7174

Closed
@alebar

Description

@alebar

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions