Sergei Ledvanov
Sergei Ledvanov

Reputation: 2251

How to replace a bean with a testing mock in Spring Boot for integration testing

I have a spring app and integration tests for this app. I would like to replace a bean with a mock bean.

My real bean looks like this

@Service
public class MyService {

}

and for testing I would like it to be replaced

@Service
public class TestMyService {

}

All I can think of is to use profiles for different services. For example:

@Service
@Profile("!test")
public class MyService implements IMyService {

}

@Service
@Profile("test")
public class TestMyService implements IMyService {

}

And then I autowire the bean like this

@Autowired
private IMyService myService;

Is there a better way?

Upvotes: 15

Views: 18848

Answers (4)

Javaru
Javaru

Reputation: 32026

For Spring Boot, @MockBean (and @SpyBean) are likely your best bets as noted in Grzegorz Poznachowski's answer.

For non Spring Boot Applications, an alternative is to use @Primary to annotate the mock/stub @Bean/@Service definition. That annotation "indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency." As such, your mock will be used over the "real" one when wiring other Beans/Configurations.

@SpringJUnitWebConfig(MyAppConfig.class)
public class MyUintTest 
{
    @Autowired
    private ServiceInterface serviceMock;

    @Autowired
    private SomeClass someClassMock;

    @Test
    public void myTest() 
    {
        when(someClassMock.getProperty()).thenReturn(properties.get());
        // . . .
    }

    @Service
    @Primary
    static class TestService implements ServiceInterface 
    {
        // . . .
    }

    @Configuration
    static class Config 
    {
        @Bean
        @Primary
        public SomeClass someClassBean() 
        {
            return Mockito.mock(SomeClass.class);
        }
    }
}

Upvotes: 0

Grzegorz Poznachowski
Grzegorz Poznachowski

Reputation: 644

Spring Boot has @MockBean and @SpyBean annotations exactly for this purpose:

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans

Declaration is simple:

@MockBean
private MyService myService;

Spring Boot will inject Mockito mock there instead of the actual bean.

Upvotes: 8

sagarr
sagarr

Reputation: 1212

You can name your beans, in your case something like

@Service("testService")    
public class TestMyService implements IMyService {

}

and in your test class, you can explicitely ask for test service using @Qualifier, like

@Qualifier("testService")
@Autowired
private IMyService myService;

Upvotes: 0

Jakub Marchwicki
Jakub Marchwicki

Reputation: 868

My personal preference is to avoid loading the compete context for testing. Therefore I like my test to focus on a subset of beans. It usually means I outline beans which I use in tests:

@RunWith(SpringRunner.class)
@SpringBootTest(
        classes = {TestMyService.class, OtherClassNeededForTesting.class}
)
public class DelaysProviderTest {

}

If more configuration is needed I tend to prepare a separate configuration class for tests:

@RunWith(SpringRunner.class)
@SpringBootTest(
        classes = MyTest.Cfg.class
)
public class MyTest {

    @Import({
        // .. some classes to import including TestMyService.class
    })
    @Configuration
    public static class Cfg {

    }

}

When even more configuration is needed (or mocking), I use the test configuration for providing appropriate mocks

@RunWith(SpringRunner.class)
@SpringBootTest(
        classes = MyTest.Cfg.class
)
public class MyTest {

    @Import({
        // .. some classes to import
    })
    @Configuration
    public static class Cfg {

        @Bean
        public IMyService service() {
            IMyService mock = Mockito.mock(IMyService.class);
            when(mock.someMethod()).thenReturn("some data");

            return mock;
        }

    }

}

Upvotes: 9

Related Questions