Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@MockBean and @SpyBean without amending the application context #34768

Open
maciejwalkowiak opened this issue Mar 26, 2023 · 12 comments
Open

@MockBean and @SpyBean without amending the application context #34768

maciejwalkowiak opened this issue Mar 26, 2023 · 12 comments
Labels
type: enhancement A general enhancement

Comments

@maciejwalkowiak
Copy link
Contributor

This is more an idea for enhancement rather than an issue.

@MockBean and @SpyBean are designed in a way that using them amends the application context. Using them in any bigger application requires care and understanding how Spring context caching works and how to optimize tests structure to avoid creating many application contexts.

Perhaps there is an option to change the behavior of @MockBean and @SpyBean to replace beans with mocks/spies in application context for the specific test case, and then replace them back with original objects. This way, these annotations would not trigger creation of the new application context.

Such behavior has been implemented in https://github.com/antoinemeyer/mock-in-bean/. The biggest problem I see with this approach is executing tests in parallel.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 26, 2023
@wilkinsona
Copy link
Member

wilkinsona commented Mar 27, 2023

I hadn't seen Mock in Bean before. Thanks for the pointer to it.

Perhaps there is an option to change the behavior of @MockBean and @SpyBean to replace beans with mocks/spies in application context for the specific test case … Such behavior has been implemented in https://github.com/antoinemeyer/mock-in-bean/

The behavior implemented by Mock in Bean seems to be subtly different. Rather than replacing beans with mocks/spies in the application context for the specific test, it replaces the value of a field in a bean for the duration of a test. While clever, I think this approach may come with too many caveats to include in Spring Boot itself. For example, I believe it only works when the item that you want to mock or spy is held in a field of a bean. It also requires this field to be mutated. That either impedes constructor-based injection and immutability or it requires reflective hacks to set a final field which may mess with memory visibility when multiple threads are involved, such as when testing with WebTestClient or TestRestTemplate.

One problem with replacing things at the bean level on a test-by-test basis is that we don't know where those beans have been injected or how they've been used while the context was being refreshed. This makes it very hard to swap in a mock or spy for the duration of a test in every place where that is needed. One option is to lazily proxy everything and then return the actual bean, a mock, or a spy, on a test-by-test basis but this brings with it a number of different problems.

I can certainly see the appeal of being able to mock and spy different beans in different tests without it resulting in many different application contexts being created. It's something that's come up more than once over the years but we've been unable to think of a good way to do it that doesn't introduce too many caveats and drawbacks. I'll leave this issue open so that we can think about it again.

@wilkinsona wilkinsona added the for: team-meeting An issue we'd like to discuss as a team to make progress label Mar 27, 2023
@ramananrpn
Copy link

Hey @wilkinsona @maciejwalkowiak , Good day!
Im a new contributor looking to start contributing to this repo! Im happy to pick with this if you are looking for hands!
Please let me know so that I can start with this with some of your insights .
Or I'll be more happy if you helping me with anything to start with like good-first-issue to get familiar with code-base as Im a first time contributor!

Awaiting your response

@wilkinsona
Copy link
Member

Thanks for your interest in contributing, @ramananrpn. We don't know what, if anything, we can do to address this requirement so it isn't a great place to get started. I'm not sure if we have any at the moment, but please keep an eye out for unassigned issues labelled with ideal for contribution or, as your haven't contributed before, first-timers only.

@ramananrpn
Copy link

Thanks @wilkinsona . Ill keep an eye in this

@philwebb philwebb changed the title @MockBean and @SpyBean without amending the application context @MockBean and @SpyBean without amending the application context May 8, 2023
@fprochazka
Copy link
Contributor

fprochazka commented May 28, 2023

I'm successfully using the following technique on my projects to avoid this problem:

@AutoConfiguration
public class TestOverridesConfiguration {
    @Primary
    @Bean
    public ExternalService externalServiceMock(@Qualifier("externalService") final ExternalService real) {
        return Mockito.mock(ExternalService.class, AdditionalAnswers.delegatesTo(real));
    }

with a custom TestExecutionListener that resets the mocks around every test.

It can probably be improved in several ways, but it is good enough for me for now. E.g. this could be wrapped semi-automatically with BeanPostProcessor, or something like that, similarly to what Spring is doing with Hibernate's EntityManager.

@philwebb philwebb added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged for: team-meeting An issue we'd like to discuss as a team to make progress labels Jun 22, 2023
@philwebb philwebb added this to the General Backlog milestone Jun 22, 2023
@Mobe91
Copy link

Mobe91 commented Oct 13, 2023

Hey @wilkinsona I'd be interested in your opinion on @fprochazka 's approach (also see his blog post). Do you see any caveats that people should be aware of before following this?

@wilkinsona
Copy link
Member

It feels similar to @MockBean and @SpyBean as it will change the application context. As long as you use TestOverridesConfiguration everywhere then that won't affect the number of contexts create by running the test suite. It probably makes that consistent configuration easier than @MockBean and @SpyBean do.

@edyda99
Copy link

edyda99 commented Dec 9, 2023

There is a library that tackles this problem MockInBean
If you want to integrate it with Spring Boot, We can contact the library owner. Or I ( familiar with both Spring framework and this library ) can do a POC under your guide! @wilkinsona
Hope to hear from you soon!!

@wilkinsona
Copy link
Member

Mock In Bean has already been discussed in this very issue. Please see the opening comment and my first reply.

@edyda99
Copy link

edyda99 commented Dec 9, 2023

Not sure How I missed that!
Thanks for your reply

@antoinemeyer
Copy link

antoinemeyer commented Dec 9, 2023

@wilkinsona , getting back to that original comment.
You mentioned:

One option is to lazily proxy everything and then return the actual bean, a mock, or a spy, on a test-by-test basis but this brings with it a number of different problems.

Could you please elaborate on those different problems you are concerned with?
(I've been working on a solution that can handle parallel test suites by proxying the fields while keeping track of all original beans/mocks per thread)

@wilkinsona
Copy link
Member

Proxying isn't without its problems and can change behavior in subtle and unpredictable ways, particularly with final classes, classes that have already been proxied, classes that call methods on themselves, and so on. Proxying every bean in the context brings those possible changes in behavior to every bean in the context and does so only for tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

9 participants