79

Is there a simple way I can easily override an autowired bean in specific unit tests? There is only a single bean of every type in the compile classes so it's not a problem for autowiring in this case. The test classes would contain additional mocks. When running a unit test I'd simply like to specify an additional Configuration that says basically, while running this unit test use this mock instead of the standard bean.

Profiles seem a bit overkill for what I require and I'm not sure this would be achievable with the Primary annotation as different unit test could have different mocks.

samblake
  • 1,517
  • 3
  • 16
  • 33
  • 2
    Have you tried @ContexConfiguration? http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/testing.html#testcontext-ctx-management-javaconfig Would you like to have different mocks for different test classes or different mocks for different test methods in the same test class? – mats.nowak Feb 19 '15 at 12:18
  • Yes, this is what I envisioned, setting my standard configuration along with the test configuration with the beans to override in the test configuration. Different mocks for a whole class would be sufficient. – samblake Feb 19 '15 at 12:53
  • Does this answer your question? [Overriding beans in Integration tests](https://stackoverflow.com/questions/35742920/overriding-beans-in-integration-tests) – Vadzim Jun 25 '21 at 04:39

6 Answers6

90

If you just simply want to provide a different bean in your tests, i think you don't need to use spring profiles or mockito.

Just do the following:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { TestConfig.class })
public class MyTest
{
    @Configuration
    @Import(Application.class) // the actual configuration
    public static class TestConfig
    {
        @Bean
        public IMyService myService()
        {
            return new MockedMyService();
        }
    }

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

NOTE: tested with spring boot 1.3.2 / spring 4.2.4

teo
  • 1,187
  • 9
  • 10
  • 18
    I got "a definition for bean ... already exists. This top-level bean definition is considered as an override." I had to change the "myService()" method to some other name e.g. "myServiceMock()". I would also recommend to use @Primary annotation (for IMyService) to make sure that the bean defined in TestConfig configuration will override the one from Application configuration. – Kacper86 Aug 30 '16 at 11:06
  • 1
    @Kacper86 it's because you're using Spring 4.1 or below, that bug was fixed in 4.2 – Pierre Nov 12 '17 at 03:05
  • 1
    What is the `@Import` annotation for? Is that only needed if you want to use existing beans in your test config? – augurar Feb 21 '18 at 20:21
  • Why the class needs to be static? – chomp May 07 '18 at 13:24
  • 4
    @Kacper86: Adding "Primary" annotation resolved Spring's complaints about having found 2 implementations for the interface. Thank you! – user1697575 Jun 22 '18 at 14:17
  • 3
    I also had to use a `@Primary` annotation to override the original bean (spring-boot-starter-parent:2.0.0.RELEASE, spring-boot-starter-test:2.0.4.RELEASE); thx @Kacper86 – Murukan Sep 28 '18 at 11:24
  • This solution is working perfectly on spring-boot-starter-parent:2.5.5. No need to use `@Primary`. I just kept the TestConfig as a solo class so I can import and all other tests too. – Mike Nov 21 '21 at 14:20
52

In Spring Boot 1.4 there's a simple way for doing that:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { MyApplication.class })
public class MyTests {
    @MockBean
    private MyBeanClass myTestBean;

    @Before
    public void setup() {
         ...
         when(myTestBean.doSomething()).thenReturn(someResult);
    }

    @Test
    public void test() {
         // MyBeanClass bean is replaced with myTestBean in the ApplicationContext here
    }
}
Sergey Shcherbakov
  • 4,534
  • 4
  • 40
  • 65
  • 4
    What is `MyApplication`? – Krzysztof Krasoń May 11 '17 at 14:23
  • 1
    You should replace that with the name of your mail application class that contains main() function and is annotated with @SpringBootApplixation – Sergey Shcherbakov May 12 '17 at 09:38
  • 2
    If you want to replace the bean with a Mockito mock, this is the best answer. Otherwise (e.g. if you want to inject a custom object) it doesn't help. – augurar Feb 21 '18 at 20:21
  • 2
    To make `@MockBean` works you need `@TestExecutionListeners(MockitoTestExecutionListener.class)`. Some other annotation or configuration can magically do it for you. Check the docs )) – gavenkoa Sep 20 '18 at 12:49
7

I had similar problem and I solved with a mix and I find this one more useful and reusable. I created a spring profile for the tests and a config class that overrides the beans I want to mock in a very simple way:

@Profile("test")
@Configuration
@Import(ApplicationConfiguration.class)
public class ConfigurationTests {

    @MockBean
    private Producer kafkaProducer;

    @MockBean
    private SlackNotifier slackNotifier;

}

By doing that I can @Autowire those mock beans and use mockito to verify on them. Main advantage is that now all tests seamlessly get the mock beans without any per-test change. Tested with:

spring boot 1.4.2

Israel Fernández
  • 395
  • 1
  • 3
  • 8
4

Since Spring Boot 1.4.0 instead of explicitly specifying @Configuration for tests, simply add static nested class annotated with @TestConfiguration and provide your replacement @Bean annotated with @Primary.

@TestConfiguration will be added to your primary Spring Boot test context (which means your production bean will still be created), but the one from @TestConfiguration will be used, because of the @Primary.

Kirill
  • 6,762
  • 4
  • 51
  • 81
  • 1
    This works, with a few notes: The test config class needs to be imported on the test, even if it's a static inner class, eg. `@Import(MyTest.TestConfig.class)`. The factory method needs to be named differently from the one in the production config (eg.`@Configuration @Bean thing(); @TestConfiguration @Primary @Bean thingMock();`) – salomvary Oct 19 '21 at 16:13
3

You should use spring profiles in order to know what kind of bean you want to use in different contexts.

http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html

Eddú Meléndez
  • 6,107
  • 1
  • 26
  • 35
-1

As mats.nowak commented, @ContextConfiguration is useful for this.

Say a parent test class is like:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/some-dao-stuff.xml"
    ,"classpath:spring/some-rest-stuff.xml"
    ,"classpath:spring/some-common-stuff.xml"
    ,"classpath:spring/some-aop-stuff.xml"
    ,"classpath:spring/some-logging-stuff.xml"
    ,"classpath:spring/some-services-etc.xml"
})
public class MyCompaniesBigTestSpringConfig {
...

Create a child test class:

package x.y.z;
@ContextConfiguration
public class MyOneOffTest extends MyCompaniesBigTestSpringConfig {
...

and put in src/test/resources/x/y/z/MyOneOffTest-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">


    <bean id="widgetsService" class="com.mycompany.mydept.myservice.WidgetsService" primary="true" />

</beans>

That widgetsService bean will override (take the place of) the bean defined in the main config xml (or Java config). See about inheritLocations Also Note the default -context.xml file. Example of that here. Update: I had to add primary="true", apparently it's needed.

awgtek
  • 1,483
  • 16
  • 28