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

Provide a safe way to override and mock beans in the TestContext framework #29917

Closed
mwisnicki opened this issue Jan 23, 2023 · 10 comments
Closed
Assignees
Labels
in: test Issues in the test module type: enhancement A general enhancement
Milestone

Comments

@mwisnicki
Copy link

Overriding beans in testing is commonly needed yet current approach is unnecessarily complicated and at the same time limited and very error prone (allow-bean-definition-overriding will hide other issues). For example see spring-projects/spring-boot#30513

It would be great if there was a single annotation that allowed overriding single bean in testing, e.g.:

@SpringBootTest
class SomeTest {

  @Bean @TestOverride
  MyBean myBean() { return new MyBean() }

}

Even better if it could be relaxed to support field instead of method.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 23, 2023
@philwebb
Copy link
Member

philwebb commented Feb 1, 2023

We think this is an interesting idea, but something that could be implemented in Spring Framework rather than Spring Boot. We'll transfer the issue for the Framework team to consider.

@bclozel bclozel transferred this issue from spring-projects/spring-boot Feb 1, 2023
@bclozel bclozel added the in: test Issues in the test module label Feb 1, 2023
@Saljack
Copy link

Saljack commented Dec 6, 2023

Or introduce an option to define which bean names or classes can be overridden.
For example in Spring Boot will be this property:

spring.main.allow-bean-definition-overriding-names=myBean

and then you can override only a bean with the name myBean

@snicoll
Copy link
Member

snicoll commented Dec 14, 2023

It would be great if there was a single annotation that allowed overriding single bean in testing, e.g.:

Unfortunately, the code snippet you've provided wouldn't work. A test class can't be a @Configuration class as we process it after the actual test context has been refreshed. Ignoring that, a method callback isn't great as the instantiation of the bean to override would happen before the test class itself is initialized. The method has to be static (like many other callbacks in tests).

Even better if it could be relaxed to support field instead of method.

I am not sure how that would look like, but it means we have to instantiate the test instance before refreshing the context (since the bean factory can instantiate that bean at any point in time).

Brainstorming with @simonbasle, we're considering several options. First an infrastructure that is inspired by the @MockBean and @SpyBean support in Spring Boot. Not replacing it but providing the building blocks so that the Spring Boot counterpart can really focus on the feature and not the plumbing. Then, a way to flag a field for overriding. To be consistent, the value should be replaced in the bean factory. An example of such usage could be:

class MyTest {

    @MethodOverride
    private MyBean myBean;


    static MyBean myBeanValueSupplier() {
        return new MyBean();
    }

Where @MethodOverride is one implementation, here based on a naming convention for the method. It can be used to override the bean, based on a more generic contract. For other use cases, you could define your own annotation and/or provide the implementation of a "processor" that would perform the swapping.

Does that make sense? Any feedback on the proposal?

@jhoeller jhoeller added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 19, 2023
@jhoeller jhoeller added this to the 6.2.x milestone Dec 19, 2023
@simonbasle simonbasle modified the milestones: 6.2.x, 6.2.0-M1 Mar 8, 2024
@sbrannen sbrannen changed the title Provide a safer way to override beans in testing Provide a safe way to override and mock beans in the TestContext framework Mar 10, 2024
@sbrannen
Copy link
Member

sbrannen commented Mar 10, 2024

Please note that this has been addressed by @simonbasle in commit e1bbdf0 which:

Introduces two sets of annotations (@TestBean on one side and @MockitoBean/@MockitoSpyBean on the other side), as well as an extension mechanism based on a new @BeanOverride meta-annotation.

Extension implementers are expected to only provide an annotation, a BeanOverrideProcessor implementation, and an OverrideMetadata subclass.

@sbrannen
Copy link
Member

The new @MockitoBean and @MockitoSpyBean annotations in Spring Framework are analogous to the existing @MockBean and @SpyBean annotations in Spring Boot.

@nmck257
Copy link

nmck257 commented Oct 21, 2024

Hi @sbrannen - question on the new annotations:
MockBean supported targeting either fields or types. MockitoBean only targets fields, and I don't see an obvious replacement for the MockBean behavior to target types.

This was useful for test cases which wanted to stub out some set of beans (ie to avoid some side effect of regular instantiation), but didn't care about specifying behavior.

Is that feature intentionally dropped? Or is it just not-yet-added? Is there discussion on this somewhere?

@sbrannen
Copy link
Member

Hi @nmck257,

MockBean supported targeting either fields or types. MockitoBean only targets fields, and I don't see an obvious replacement for the MockBean behavior to target types.

This was useful for test cases which wanted to stub out some set of beans (ie to avoid some side effect of regular instantiation), but didn't care about specifying behavior.

Is that feature intentionally dropped? Or is it just not-yet-added? Is there discussion on this somewhere?

If you are referring to the ability to declare @MockBean at the class-level, that feature is intentionally not implemented in Spring Framework's @MockitoBean support. The "Bean Override" feature set in the Spring TestContext Framework is only supported for fields meta-annotated with @BeanOverride.

If you want to mock a bean by type, you can declare @MockitoBean on a field of that type. You do not have to actually use that field within your test class.

Does that answer your questions?

Cheers,

Sam

@nmck257
Copy link

nmck257 commented Oct 22, 2024

@sbrannen - yes, I see how that can work, though I think the pattern has drawbacks.

Suppose you have multiple @SpringBootTest classes, and want to stub out the same set of beans for each of them. With an annotation targeting the class itself, you could define a meta-annotation containing those repeated invocations of MockBean and reuse wherever needed. If we only have support for mocking beans as fields, then the next alternative would be a common superclass which declares those annotated fields. That pattern pushes you to (single) inheritance, whereas the class annotation pattern was composition-friendly.

Subjectively, I also think that if the test developer's intent is not to define any behavior for the mocked bean, then it's easier to read and maintain if we can avoid adding an unused field to the class scope.

@sbrannen
Copy link
Member

sbrannen commented Nov 20, 2024

Thank for the feedback, @nmck257.

I understand your concerns, but supporting @MockitoBean at the class-level was something the team decided not to do in Spring Framework.

However, feel free to create a new GitHub issue to discuss the merits or adding support for that. I cannot promise that we will implement it, but we are open to discussing it.

For what it's worth, someone else just raised this issue in the Spring Boot issue tracker as well.

Regards,

Sam

@nmck257
Copy link

nmck257 commented Nov 20, 2024

@sbrannen -- sounds good. I've prepped #33925 for discussion.

kaladay added a commit to folio-org/spring-module-core that referenced this issue Feb 6, 2025
Address newly deprecated code issues.

Address ambiguous `any()` issue that now appears and breaks tests.

The `@MockBean` is no longer available and we must instead use `@MockitoBean`.
see: spring-projects/spring-framework#29917 (comment)
see: spring-projects/spring-framework#33925
see: https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-mockitobean.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

10 participants