MockitoBean is designed to replace the now-deprecated MockBean annotation. MockBean could target either a field or a type. Currently, MockitoBean can only target a field. This issue proposes adding the support to target types to MockitoBean, similar to MockBean.

(pasting some prose from #29917 comments)

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

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.

Here's some Kotlin code to help demonstrate the point above:

@MockBeans(
    MockBean(MyServiceWithOutOfProcessSideEffectsOnBoot::class),
    MockBean(MyPreemptiveClientAuthTokenFetcher::class),
    MockBean(MyServiceWhichLoadsOneMillionThingsIntoMemoryOnBoot::class),
)
annotation class MockExpensiveDependencies

@SpringBootTest
@MockExpensiveDependencies
class BusinessLogicControllerTest {
    // ...
}

@SpringBootTest
@MockExpensiveDependencies
class SomeOtherTest {
    // ...
}

vs

open class MockExpensiveDependenciesBase {
    @MockBean
    private lateinit var foo: MyServiceWithOutOfProcessSideEffectsOnBoot
    @MockBean
    private lateinit var bar: MyPreemptiveClientAuthTokenFetcher
    @MockBean
    private lateinit var gav: MyServiceWhichLoadsOneMillionThingsIntoMemoryOnBoot
}

@SpringBootTest
class BusinessLogicControllerTest : MockExpensiveDependenciesBase() {
    // ...
}

@SpringBootTest
class SomeOtherTest : MockExpensiveDependenciesBase() {
    // ...
}

Comment From: OrangeDog

Here's a couple of other cases that @MockBean allowed.

Including mocks via additional config (not just other annotations):

@TestConfiguration
@MockBean({
    MyServiceWithOutOfProcessSideEffectsOnBoot.class,
    MyPreemptiveClientAuthTokenFetcher.class
})
@EnableSomething
@Import(OtherStuff.class)
public class SharedTestConfiguration { }

@SpringTestAnnotation
@Import({SharedTestConfiguration.class, SomeOtherConfig.class})
public class SomeTests {

If one of these mocks were autowired, stub behaviour could then be specified. This doesn't work when returning a mock from a @Bean method, depending on how beans are proxied:

@SpringTestAnnotation
@MockBean(MyServiceWithOutOfProcessSideEffectsOnBoot.class)  // actually declared elsewhere
public class SomeTests {
    @Autowired private MyServiceWithOutOfProcessSideEffectsOnBoot myService;

    @Test
    public void onlyThisTestSpecificallyWantsToStubIt() {
        doReturn(true).when(myService).isSomething();
    }

Comment From: jack5505

Can I take this issue to work on this

Comment From: sbrannen

Hi @jack5505,

The team has not yet made a decision regarding this issue, and it's highly unlikely that the implementation would be performed by anyone outside the core team.

Thanks for the offer anyway.

Comment From: sbrannen

We appreciate the constructive feedback we have received here as well as in #33934, and in light of that we are considering introducing support for declaring @MockitoBean at the class level on test classes, with the driving impetus being the ability to reuse mock configuration across a test suite without having to extend a base test class.

If we choose to do so, that would allow @MockitoBean to be declared directly (or as a meta-annotation) on a test class (or on any superclass or interface in the type hierarchy of the test class). However, that does not mean that @MockitoBean would be supported on a @Configuration class.

In terms of declaring multiple types to mock (with different mock configurations), that could be supported by allowing @MockitoBean to be used as a repeatable annotation or introducing a dedicated, required container annotation such as @MockitoBeans.

In any case, @MockitoBean would need a new attribute to allow the developer to specify which type or types to mock, since the type could no longer be inferred from the annotated field's type.

The following demonstrates what such a programming model might look like.

@SpringJUnitConfig
// Register one or more types to mock
@MockitoBean(types = { ExampleService.class, AnotherService.class })
class MyIntegrationTests {

    // Optionally inject the mocks for stubbing and verification.
    //
    // @Autowired
    // ExampleService exampleService;
    //
    // @Autowired
    // AnotherService anotherService;

    @Test
    void test() {
        // ...
    }
}

For reuse of the mock configuration across the test suite, teams could introduce composed annotations such as the following.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = { ExampleService.class, AnotherService.class })
public @interface SharedMocks {
}

That would allow @SharedMocks to be declared on any test class (or on any superclass or interface in the type hierarchy of the test class) as follows.

@SpringJUnitConfig
// Reuse @MockitoBean declarations via a composed annotation:
@SharedMocks
class MyIntegrationTests {

    // Optionally inject the mocks for stubbing and verification.
    //
    // @Autowired
    // ExampleService exampleService;
    //
    // @Autowired
    // AnotherService anotherService;

    @Test
    void test() {
        // ...
    }
}

Another option would be to limit one type per @MockitoBean annotation, such as in the following example, where type can only supply a single Class reference to mock.

@SpringJUnitConfig
@MockitoBean(type = ExampleService.class)
@MockitoBean(type = AnotherService.class)
class MyIntegrationTests {

    // Optionally inject the mocks for stubbing and verification.
    //
    // @Autowired
    // ExampleService exampleService;
    //
    // @Autowired
    // AnotherService anotherService;

    @Test
    void test() {
        // ...
    }
}

NOTE: In contrast to Spring Boot's @MockBean annotation, the type or types to mock cannot be specified via an explicit or implicit value attribute since @MockitoBean already has a value attribute which is an alias for the name attribute.


As I mentioned previously, we are not yet committed to implementing this feature, but we are considering it.

Thus, if you are interested in this GitHub issue or #33934, we would appreciate your feedback on whether the above proposal would satisfy your needs to reuse @MockitoBean across your test suite.

As a side note, we would also be interested in knowing how many Spring Boot users have traditionally declared @SpyBean on a test class as opposed to on a field in a test class.

Thanks,

Sam

Comment From: cfredri4

The composed annotation example would solve my cases. We're not using @SpyBeanon a test class.

Comment From: nmck257

Thanks for the update @sbrannen !

Here are some responses to topics raised in your comment: - Targeting only test classes (and not Configuration classes) is suitable for my usage - No preference on repeatable vs container annotations; I think I recall that the container pattern predates JDK support for repeatable annotations, and the repeatable pattern may be a bit simpler? - I have not used @SpyBean on a test class; my intuition is that I would only use SpyBean for an element which I expect to reference directly in my test cases, and if I expect to reference a (stubbed) bean in my test cases, then declaring it as a field is less code anyway compared to declaring the stubbing at the class level and autowiring the resultant bean into a field - Adding a type field makes sense, though it prompts a design decision about how to handle contexts which declare multiple beans of that same type. For the old MockBean, the name was required in that case, but it may also be useful to mock all beans of that type if the name is not specified

Comment From: Saljack

Maybe it's a stupid idea, but isn't it better to create a new annotation for it? Because I feel it is misuse for @MockitoBean. If it was originally designed differently than @MockBean why not introduce a new annotation? That can be used also for spy bean with an attribute (I do not use it on classes). People will need to change their code anyway so it does not matter if there are two annotions one for field and one for classes. For example:

@MockitoBeanClass(value=ExampleService.class, spy=SpiedService.class)
@MockitoBeanClass(OtherExampleService.class)

Comment From: JanMosigItemis

Hi and many thanks for providing an open discussion on this topic 👍

In my current project, we used to heavily rely on @MockBean and @MockBeans at the class level. That helped us to create custom test annotations which helped to reduce "white mocking" noise.

For example:

@SpringBootTest
@Import({...configs...})
@MockBeans({@MockBean(ClassA.class), @MockBean(ClassB.class)})
public @interface BookingTest {}
@BookingTest
class BookingPaymentIT {

  @Test
  void testSomething() {
    // ...
  }
}

As opposed to:

@SpringBootTest
@Import({...configs...})
class BookingPaymentIT {

  @MockitoBean
  private ClassA classAMock;

  @MockitoBean
  private ClassB classBMock;

  @Test
  void testSomething() {
    // ...
  }
}

Also this reduced redundancy, because we could reuse the mock "definitions".

Although with Spring Boot 3.4.x this may also be achieved in a different way, we found the old way to be very convenient and straightforward.

Comment From: sergerot

Another shorter annotation variant could be something like:

@MockitoBean(type = {FirstService.class, SecondService.class})
class MyIntegrationTests {
}

Comment From: emouty

On my projects, we heavily rely on @MockBean for our ITs. Our standard way of doing things currently is having a @Component class per @MockBean with helpers in it. Then we have a class that implements BeforeEachCallback AfterEachCallback in which we get the mocked bean from the Spring context in order to initialize mocked responses that are needed every time. A Simplified example would be something like

@Component
public class ClientAMockBean {
    @MockBean
    private ClientA clientA;

    public void stubInitCatalog(){
        when(clientA).getCatalog()).thenReturn(initCatalog()));
    }
    public void stubGetLiveProduct(){
        when(clientA).getProduct(any()).thenReturn(new Product(UUID.randomUUID()));
    }
    public void stubPurchaseProduct(UUID id){
        when(clientA).buyProduct(eq(id)).thenReturn(new Transaction(UUID.randomUUID(),id));
    }
    //...
}
public class ITCommonCallbacks implements BeforeEachCallback, BeforeAllCallback{

    private ClientAMockBean clientAMockBean;

    @Override
    public void beforeAll(ExtensionContext context) {
        ApplicationContext springContext = SpringExtension.getApplicationContext(context);
        clientAMockBean = springContext.getBean(ClientAMockBean.class);
    }
    @Override
    public void beforeEach(ExtensionContext extensionContext) {
        clientAMockBean.stubInitCatalog();
    }
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({ ITCommonCallbacks.class })
class ProductPurchaseIT {
    @Autowired
    private ClientAMockBean clientAMockBean;
    @Autowired
    AppServer server;
    @Test
    void shouldBuyProduct(){
        var user = server.initUserCases();
        var productA = user.getFirstCatalogProduct();
        clientAMockBean.stubPurchaseProduct(productA.id())
        //...
    }

Ideally I would have liked to be able to have @MockitoBean on @Configuration but that's not planned based on https://github.com/spring-projects/spring-framework/issues/33934 . @MockitoBean at the type level could fit because I could group all my mocks into a single @SharedMocks annotation, thus easily reuse them across ITs and then I could inject them in the mock helper class.

@TestComponent("clientAMockBean")
public class ClientAMockBean {
    @Autowired
    private ClientA clientA;

    public void stubInitCatalog(){
        when(clientA).getCatalog()).thenReturn(initCatalog()));
    }
    public void stubGetLiveProduct(){
        when(clientA).getProduct(any()).thenReturn(new Product(UUID.randomUUID()));
    }
    public void stubPurchaseProduct(UUID id){
        when(clientA).buyProduct(eq(id)).thenReturn(new Transaction(UUID.randomUUID(),id));
    }
    //...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = { ClientA.class, AnotherService.class })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({ ITCommonCallbacks.class })
public @interface SpringBootIT {
}
@SpringBootIT
class ProductPurchaseIT {
    @Autowired
    private ClientAMockBean clientAMockBean;
    @Autowired
    AppServer server;
    @Test
    void shouldBuyProduct(){
        var user = server.initUserCases();
        var productA = user.getFirstCatalogProduct();
        clientAMockBean.stubPurchaseProduct(productA.id())
        //...
    }

Comment From: daniel-frak

Chiming in to say that the ability to declare @MockitoBean as part of a meta-annotation on test classes would solve my use case, as well.

I currently have several annotations in my project, such as @ControllerTest, @JpaRepositoryTest and @WebSocketPublisherTest and each of these provides its own @MockBeans, so that the same Spring Contexts can be reused between controller tests, repository tests and so on.

I'm currently declaring @MockBean on @TestConfiguration classes, but I could just as easily define @MockitoBean on the meta-annotations themselves (if that was allowed).

I am eagerly hoping that you decide to support this.

Comment From: sbrannen

Hi everybody,

Thanks for all of the feedback!

The current plan is to support @MockitoBean as a repeatable annotation that can be declared on a test class (or one of its superclasses or implemented interfaces). This would naturally allow @MockitoBean to be used as a meta-annotation on a composed annotation declared at the test class level, which would allow the mock configuration to be reused across test classes via a single composed annotation.

This issue is currently assigned to the 6.2.x backlog and "pending design work", with the hope of getting this feature implemented in time for 6.2.2.

Regards,

Sam

Comment From: sbrannen

Update: current work on this can be viewed in the following feature branch.

  • https://github.com/spring-projects/spring-framework/compare/main...sbrannen:spring-framework:issues/gh-33925-MockitoBeans

MockitoBeansByTypeIntegrationTests and MockitoBeansByNameIntegrationTests demonstrate most supported use cases for @MockitoBean at the class/interface level.

Comment From: sbrannen

This new feature was released today with Spring Framework 6.2.2.

Please check out the updated documentation for details and examples!

Comment From: boris-faniuk-n26

Hi, there! Just came across this issue after I updated to Spring Boot 3.4. I just wanted to mention that I do use SpyBean on class level. I have a base test configuration where some beans are declared as SpyBean so that I can spy on them without re-declaring this on every next test class. As I understand the requested feature was only added for MockitoBean, but I would appreciate if this was added for MockitoSpyBean as well. mockk supports this, previous spring boot supported this, I think it is reasonable to keep this option.

Comment From: sbrannen

Hi @boris-faniuk-n26,

I just wanted to mention that I do use SpyBean on class level.

Thanks for mentioning that.

I created #34408 for us to investigate the feasibility of supporting @MockitoSpyBean at the type level on test classes (though, like with this issue, not on @Configuration classes).

Regards,

Sam

Comment From: OrangeDog

Just to note that unlike @MockBean, @MockitoBean only works on test classes, not configuration classes.

Comment From: dominioon

Just to note that unlike @MockBean, @MockitoBean only works on test classes, not configuration classes.

Are there plans to fix it?

Comment From: tobias-lippert

No there are not, see #33934