Versions Spring Boot 3.4.0 Spring Framework 6.2.0

Hi Spring team,

While upgrading our apps to Spring Boot 3.4.0, I've encountered that some of our tests don't start because the application context cannot be created due to a missing bean dependency that should exist as mocked bean.

We define some MockitoBeans in a config class annotated with @TestConfiguration and import this class using @Import. When I define a MockBean instead, the tests succeed.

You can find an example that reproduces the behavior here: https://github.com/tobias-lippert/spring-mockitobean-test-configuration-import-bug The test passes on the branch mockbean-working and fails on the branch mockitobean-not-working.

I couldn't find anything in the docs pointing out that this should no longer work. If this is not a bug, but intended behavior please advise how to fix this. The easiest, but not the prettiest workaround I've found is getting rid of the separate TestConfigurations and placing all MockitoBeans in the actual test class.

Comment From: olsavmic

We have encountered the same problem.

The reason why we define @MockitoBean (and @MockitoSpyBean) is to prevent new Application Context creation - mocks are defined in one place and used by all tests importing the same configuration.

We could use a base class for tests but that goes against the extensible design of JUnit 🤔

Comment From: EugenMayer

Everything in the issue queue points to MockitoBean/MockitoSpyBean has not been field test well (at all?). There are so many limitations right now, so that deprecating MockBean/MockSpy sounds like a well overpaced decision that should be rolled back with 3.4.1 until MockitoBean/MockitoSpyBean is actually able to replace the main cases in the first place.

I have nothing agains a better implementation, but this is not a "toy" to play around, neither is the migration "a little thing". For us, this is huge i feeling pushed into a playground in this area feels like being let down. This field needs to be rock solid - and when it is, deprecation can come in.

Expamples

  1. MockitoBean cannot be used on a class level to mock away a list of bean which one will not operate on - they are not important to the setup, they are just needed to be available. This is currently not possible
  2. MockitoBean as a field annotation does not behahave the same way as MockBean in several cases, i had to roll back in several cases until i decided to rollback the entire migration in the first place

there are other issues in the queue pointing at similar, severe issues.

I beg you, please roll back the deprecation until the actual target to migrate to is "ready for battle".

Comment From: adase11

I can confirm I'm running into the same issues when moving from spring boot 3.3.5 to 3.4.0 (and thus Spring Framework 6.2 ). Much like @olsavmic we have one shared configuration class for all tests - specifically @WebMvcTest tests. We do this because there are some beans involved in web security configuration that we need mocked for these tests and they are the same across all of those tests.

The test's that are failing due to missing beans are set up almost exactly like @tobias-lippert 's example. With a @WebMvcTest that uses @Import to pull in the configuration class in which we have those mock beans declared.

I also confirmed that adding the mock bean declarations to the test class (as well as alternatively a base abstract test class) directly fixes the problem (like @tobias-lippert mentioned).

Comment From: cfredri4

Same here. We also define @MockBean in a separate @TestConfiguration and this does not work with @MockitoBean.

Comment From: adase11

I mentioned this on the Spring Boot repo https://github.com/spring-projects/spring-boot/issues/43282 . @TestConfiguration and @WebMvcTest are both from Spring Boot.

Comment From: wilkinsona

I'm a Spring Boot maintainer and only speaking for the Boot team below.

Everything in the issue queue points to MockitoBean/MockitoSpyBean has not been field test well (at all?). There are so many limitations right now, so that deprecating MockBean/MockSpy sounds like a well overpaced decision that should be rolled back with 3.4.1 until MockitoBean/MockitoSpyBean is actually able to replace the main cases in the first place.

There's a tricky balance to strike here. Replacing @MockBean and @SpyBean with something built on Spring Framework's support for bean replacement is definitely the right thing to do. To do it well, we need feedback from the community. Unfortunately, our experience shows that if something isn't deprecated, it's very hard to get the feedback that we need and we just end up in a situation very similar to the one that we're in now, albeit 6-12 months later.

I beg you, please roll back the deprecation until the actual target to migrate to is "ready for battle".

IMO, rolling back the deprecation would be counter-productive. Instead, I'd like to try to reassure folks by noting that functionality deprecated in Spring Boot 3.4 won't be going away any time soon. We have Spring Boot 3.5 planned for May 2025 and it won't be removed in that release. Assuming the same pattern as previous releases, OSS support for Spring Boot 3.5 would then be provided until May 2026.

Please keep the constructive feedback coming and then address the deprecation either through a suppression or by using an alternative, even if it is one where you feel there's room for improvement.

Comment From: EugenMayer

The important first

Please keep the constructive feedback coming and then address the deprecation either through a suppression or by using an alternative, even if it is one where you feel there's room for improvement.

I understand the critic above is harsher in it's tone, but i really try to keep it constructive in the first place (and i gave suggestions / opinions on how to improve it)

There's a tricky balance to strike here. Replacing @MockBean and @SpyBean with something built on Spring Framework's support for bean replacement is definitely the right thing to do. To do it well, we need feedback from the community. Unfortunately, our experience shows that if something isn't deprecated, it's very hard to get the feedback that we need and we just end up in a situation very similar to the one that we're in now, albeit 6-12 months later.

I'am following the project rather open-minded and i never heard of the MockitoBean/MockitoSpyBean effort, nor it's release nor the question for feedback. Can you point me at sources like release notes, blogs or anything else where this has been asked for 6-12 months ago?

Comment From: wilkinsona

I was talking in general terms about our experience with getting a sizeable portion of the user base to try out a new feature, not specifically the introduction of @MockitoBean and @MockitoSpyBean. Regardless, I think this is a case in point as https://spring.io/blog/2024/04/16/spring-framework-6-2-0-m1-overriding-beans-in-tests, published in April 2024, introduced the new Framework feature and invited feedback and suggestions for improvement. This was reinforced in Spring Boot 3.4.0-M1, released in July 2024, where @MockBean and @SpyBean were deprecated in favor of @MockitoBean and @MockitoSpyBean.

Comment From: EugenMayer

I was talking in general terms about our experience with getting a sizeable portion of the user base to try out a new feature, not specifically the introduction of @MockitoBean and @MockitoSpyBean. Regardless, I think this is a case in point as https://spring.io/blog/2024/04/16/spring-framework-6-2-0-m1-overriding-beans-in-tests, published in April 2024, introduced the new Framework feature and invited feedback and suggestions for improvement. This was reinforced in Spring Boot 3.4.0-M1, released in July 2024, where @MockBean and @SpyBean were deprecated in favor of @MockitoBean and @MockitoSpyBean.

That's fair - missed all of those. That one is one me and surely proves you point of not getting substantial feedback if it is not thrown into our faces.

Yes, but i have a "but". Use the deprecation as a tool, for a annotation that is used 100+ in the code base (triggering a warning for each), for this specific case, was just not a good choice. For us, it is rather 1000+ and this means, every CI checking testing logs needs massive scrolling, not to talk about proper deprecation warnings which are entirely hidden now. For me, you used a bazooka here (sorry if this sounds harsh again).

Constructively, i would love to offer something like - "use this in gradle to suppress the deprecation" - but there is none (all or nothing) - add "suppress to every MockBean/SpytBean usage .. well this means cluttering more then 100+ places with an @SupressWarning - one can surely use string/replace, but this is really... a lot of clutter.

So honestly, i have no constructive option on how to fix what has been enforced here while MockitoBean is not in shape (yet).

If you ask me, you could have make the entire MockitoBean migration visible in the test, when you detect it's usage and showing a warning in the search logs (once), and this warning should be able to be deactivated via properties (but is on by default). This is a sensible way of doing this, and still get visibility / exposure for feedback.

Do not get me wrong, things move on, break and needs migration. That is what it is and we all have to move. This is fair. But currently one cannot even migrate and fix it, we are just forced to take it.

I understand, it's probably too late, but at least take my feedback as bring attention to changing such sensible topics. This is not "spring session redis" or "spring security SAML", "jpa" only affecting parts of spring boots ecosystems.

This changed affected all ecosystems at once, all of them. And this makes this a sensible one.

Thank you for your feedback and comment, really appreciated to at least have a dialog - maybe we do not agree, but still, thank you for being responsive.

Comment From: HenrikPublic

When your policy is to compile with -Werror it's even more frustrating ;-)

Comment From: vpavic

When your policy is to compile with -Werror it's even more frustrating ;-)

Same frustrations here, and it's a bit ironic since both Spring Framework and Spring Boot use those as well :slightly_smiling_face::

  • https://github.com/spring-projects/spring-framework/blob/64f93d5755861e35465202c424bcb62613266b12/buildSrc/src/main/java/org/springframework/build/JavaConventions.java#L55-L56
  • https://github.com/spring-projects/spring-boot/blob/32433e84f3048c397aef3c3a8d63a0ae1ca7d5fc/buildSrc/src/main/java/org/springframework/boot/build/JavaConventions.java#L231-L232

Using suppressions tactically here and there is OK, but for non-trivial projects this can easily mean hundreds of suppressions which is really not cool.

I understand and agree that it's harder to get quality feedback without doing deprecations, but at the same time there appear to be quite a few common cases that developers rely on and don't have a clear migration path so I feel like this could easily turn out to be a prohibitively expensive upgrade for some projects (which also translates into less feedback).

So I'm personally in the camp that would like to see the deprecation rolled back, at least until clear migration path for common use cases is provided.

Comment From: sbrannen

Team Decision

We appreciate all of the feedback from the community on this topic, and we understand your concerns regarding the reuse of mock configuration.

Unfortunately, we do not have any plans to support @MockitoBean or @MockitoSpyBean on a @Configuration class or on fields within a @Configuration class.

In general, the test Bean Override support introduced in Spring Framework 6.2 is designed to be tied directly to test classes, and currently only to fields in test classes.

However, we are considering introducing support for declaring @MockitoBean at the type level for test classes. Please see https://github.com/spring-projects/spring-framework/issues/33925#issuecomment-2512070056 for details, and we encourage you to provide additional feedback there.

In light of the above, I am closing this issue.

Regards,

Sam

Comment From: wilkinsona

For those wishing to continue using @MockBean with Boot 3.4 but not enjoying wrestling with the deprecation warnings, one approach is to declare a custom @MockBean annotation that's meta-annotated with Boot's @MockBean:

package com.example;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.mockito.Answers;
import org.mockito.MockSettings;

import org.springframework.boot.test.mock.mockito.MockReset;
import org.springframework.core.annotation.AliasFor;

@SuppressWarnings("removal")
@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@org.springframework.boot.test.mock.mockito.MockBean
public @interface MockBean {

    /**
     * The name of the bean to register or replace. If not specified the name will either
     * be generated or, if the mock replaces an existing bean, the existing name will be
     * used.
     * @return the name of the bean
     */
    @AliasFor(attribute = "name", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    String name() default "";

    /**
     * The classes to mock. This is an alias of {@link #classes()} which can be used for
     * brevity if no other attributes are defined. See {@link #classes()} for details.
     * @return the classes to mock
     */
    @AliasFor(attribute = "classes", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    Class<?>[] value() default {};

    /**
     * The classes to mock. Each class specified here will result in a mock being created
     * and registered with the application context. Classes can be omitted when the
     * annotation is used on a field.
     * <p>
     * When {@code @MockBean} also defines a {@code name} this attribute can only contain
     * a single value.
     * <p>
     * If this is the only specified attribute consider using the {@code value} alias
     * instead.
     * @return the classes to mock
     */
    @AliasFor(attribute = "value", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    Class<?>[] classes() default {};

    /**
     * Any extra interfaces that should also be declared on the mock. See
     * {@link MockSettings#extraInterfaces(Class...)} for details.
     * @return any extra interfaces
     */
    @AliasFor(attribute = "extraInterfaces", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    Class<?>[] extraInterfaces() default {};

    /**
     * The {@link Answers} type to use on the mock.
     * @return the answer type
     */
    @AliasFor(attribute = "answer", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    Answers answer() default Answers.RETURNS_DEFAULTS;

    /**
     * If the generated mock is serializable. See {@link MockSettings#serializable()} for
     * details.
     * @return if the mock is serializable
     */
    @AliasFor(attribute = "serializable", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    boolean serializable() default false;

    /**
     * The reset mode to apply to the mock bean. The default is {@link MockReset#AFTER}
     * meaning that mocks are automatically reset after each test method is invoked.
     * @return the reset mode
     */
    @AliasFor(attribute = "reset", annotation = org.springframework.boot.test.mock.mockito.MockBean.class)
    MockReset reset() default MockReset.AFTER;

}

This allows the deprecation warning to be suppressed in a single place. It should be a drop-in replacement for org.springframework.boot.test.mock.mockito.MockBean that can be used by updating the relevant import statements to pull in com.example.MockBean instead.

Comment From: KrohnicDev

Other solution is to declare mocks as normal beans like this:

@TestConfiguration
public class MyTestConfiguration {

    @Bean
    MyBean myBean() {
        return Mockito.mock(MyBean.class);
    }
}

Comment From: shabbeer4a1

For those wishing to continue using @MockBean with Boot 3.4 but not enjoying wrestling with the deprecation warnings, one approach is to declare a custom @MockBean annotation that's meta-annotated with Boot's @MockBean:

```java package com.example;

import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;

import org.mockito.Answers; import org.mockito.MockSettings;

import org.springframework.boot.test.mock.mockito.MockReset; import org.springframework.core.annotation.AliasFor;

@SuppressWarnings("removal") @Target({ ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented @org.springframework.boot.test.mock.mockito.MockBean public @interface MockBean {

/* * The name of the bean to register or replace. If not specified the name will either * be generated or, if the mock replaces an existing bean, the existing name will be * used. * @return the name of the bean / @AliasFor(attribute = "name", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) String name() default "";

/* * The classes to mock. This is an alias of {@link #classes()} which can be used for * brevity if no other attributes are defined. See {@link #classes()} for details. * @return the classes to mock / @AliasFor(attribute = "classes", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) Class<?>[] value() default {};

/* * The classes to mock. Each class specified here will result in a mock being created * and registered with the application context. Classes can be omitted when the * annotation is used on a field. *

* When {@code @MockBean} also defines a {@code name} this attribute can only contain * a single value. *

* If this is the only specified attribute consider using the {@code value} alias * instead. * @return the classes to mock / @AliasFor(attribute = "value", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) Class<?>[] classes() default {};

/* * Any extra interfaces that should also be declared on the mock. See * {@link MockSettings#extraInterfaces(Class...)} for details. * @return any extra interfaces / @AliasFor(attribute = "extraInterfaces", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) Class<?>[] extraInterfaces() default {};

/* * The {@link Answers} type to use on the mock. * @return the answer type / @AliasFor(attribute = "answer", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) Answers answer() default Answers.RETURNS_DEFAULTS;

/* * If the generated mock is serializable. See {@link MockSettings#serializable()} for * details. * @return if the mock is serializable / @AliasFor(attribute = "serializable", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) boolean serializable() default false;

/* * The reset mode to apply to the mock bean. The default is {@link MockReset#AFTER} * meaning that mocks are automatically reset after each test method is invoked. * @return the reset mode / @AliasFor(attribute = "reset", annotation = org.springframework.boot.test.mock.mockito.MockBean.class) MockReset reset() default MockReset.AFTER;

} ```

This allows the deprecation warning to be suppressed in a single place. It should be a drop-in replacement for org.springframework.boot.test.mock.mockito.MockBean that can be used by updating the relevant import statements to pull in com.example.MockBean instead.

Hi @wilkinsona, I have tried to create a custom annotation for @MockBean as mentioned here, but org.springframework.boot.test.mock.mockito.MockReset enum is used in this custom annotation is also deprecated in version 3.4 .

Comment From: bclozel

@shabbeer4a1 I think the goal is not to provide a permanent solution, but rather help out in the short term with deprecations for the transition period. This annotation is itself annotated with @SuppressWarnings("removal"). You can do the same in places where you use MockReset with this annotation and warnings are raised.

@SuppressWarnings("removal")
@MockBean(classes = Something.class, reset = MockReset.BEFORE)

Comment From: michaldo

@sbrannen please show me how to solve my case with MockBean -> MockitoBean

My production code is something like:

@Service class A{
  public int a() { return 7;}
}

@Service class B{
  int b;
  public B(A a) { b = a.a();}
}

I want test to verify what happens when A returns 8. I followed https://www.baeldung.com/spring-configure-mockbean-components-before-application-start#3-using-mockbean-in-a-nested-test-configuration-class

@SpringJUnitConfig
class BTest() {

  @TestConfiguration
  static class a_returns_8 {
    @MockBean A a;
    @PostConstruct void let_a_return_8() { when(a.a()).thenReturn(8))}
  }

  @Autowired B b;

  @Test
  void b_is_8() {
    assertThat(b.b).isEqualTo(8));
  }

How to implement the test without MockBean?

Comment From: sbrannen

@michaldo, you can implement your @Configuration and test classes as follows.

@SpringJUnitConfig
class BTest {

    @MockitoBean
    A a;

    @Test
    void b_is_8(@Autowired B b) {
        assertThat(b.b).isEqualTo(8);
    }


    @Configuration
    @Import({A.class, B.class})
    static class Config {

        @Autowired
        private A a;

        @PostConstruct
        void let_a_return_8() {
            when(this.a.a()).thenReturn(8);
        }
    }

    static class A {
        public int a() {
            return 7;
        }
    }

    static class B {
        private final int b;

        public B(A a) {
            this.b = a.a();
        }
    }

}

Comment From: michaldo

Thanks. Bit more complex than previously, isn't it?

Comment From: justinTangren

Other solution is to declare mocks as normal beans like this:

```java @TestConfiguration public class MyTestConfiguration {

 @Bean
 MyBean myBean() {
     return Mockito.mock(MyBean.class);
 }

} ```

I tried this, and it did not work for me. I found that tests which called verify(myBeanMock, times(3)).someMethod() started failing because the mocks were not being reset after each test (i.e. they were seeing the counts from all tests instead of just the one test)


I found some documentation which had an example for using the new @MockitoBean in a custom annotation. Here is an expanded version of that example that I came up with. Moving my usage of @MockBean out of a test configuration file into a new custom annotation was not too hard.

@TestConfiguration
public class ComponentTestConfig {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {
    Foo.class, 
    Bar.class,
    ...
}) 
public @interface ComponentTestMocks {
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringJUnitConfig(ComponentTestConfig.class)
@ComponentTestMocks
public class FooBarComponentTest {
}

Comment From: Persi

@justinTangren You are the man! ;) Works great here!

Comment From: sbrannen

Other solution is to declare mocks as normal beans like this:

```java @TestConfiguration public class MyTestConfiguration {

@Bean
MyBean myBean() {
    return Mockito.mock(MyBean.class);
}

} ```

As others have pointed out, using that approach results in the mocks not being reset before/after test methods.

To achieve that, you can make use of Spring's MockReset.before() or MockReset.after() functionality as follows.

@Bean
MyBean myBean() {
    return Mockito.mock(MyBean.class, MockReset.after());
}

Comment From: duoduobingbing

We had the problem that we had a lot of tests with Conditionals on Configuration classes like

@Configuration
@Conditional(....)
public class SomeTestConfig {

  @MockBean
  ExampleBean example;

  @MockBean
  ExampleBean2 example2;

  ...

}
...
@Import({SomeTestConfig.class, SomeTestConfig2.class, ...})
public class SomeTest {
  ...
}

Sadly, this does not mix well with defining all needed beans in an annotation like the reference suggests with @SharedMocks.

So we now have a library for that in case someone needs beanoverride discovery in @Configuration classes as well.