Home > Software design >  Spring integration testing with mocked dependencies
Spring integration testing with mocked dependencies

Time:01-03

I'm using Spring Boot. I have Service class (annotated @Service) with a method annotated with @Retryable.

For one of my integration test, I would like to load the Service bean to the context (with all of it's configurations and annotations).

The service has several dependencies. For example,

@Service
@EnableRetry
public class MyService{
    private final BeanOne beanOne;
    private final BeanTwo beanTwo;

    public MyService(BeanOne beanOne, BeanTwo beanTwo){
        this.beanOne = beanOne;
        this.beanTwo = beanTwo
    }
    
    @Retryable(value = RuntimeException.class , maxAttempts = 3)
    public void serviceAction(){
        beanOne.doSomething();
        beanTwo.doSomething();
        doMoreThings();
    }

    private void doMoreThings(){
        //things
        //eventually throws Runtime Exception

    }
}

I want to check that I indeed implemented everything regarding the retry correctly.

I'm trying to write a Spring integration test, when mocking the dependency beans, but still loading the MyService bean to the context (to check the retryable).

I tried mocking the dependency beans (using JUnit4 and Mockito):

@Import({MyService.class})
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
    @Autowired
    private MyService myService;
    
    @MockBean
    private BeanOne beanOne;

    @MockBean
    private BeanTwo beanTwo;

    @Test
    public void test(){
        myService.serviceAction();
        verify(beanTwo,times(3)).doSomething();
    }
}

However, I'm getting NullPointerException on the beanOne.doSomething() call. It seems like beanOne and beanTwo fields in MyServiceIntegrationTest class are indeed mocked, but are not autowired to the actual myService, and instead myService's fields are null. Note that the @Retryable annotation indeed works (and repeats 3 times), however this is since NullPointerException is a RuntimeException. The code never actually reaches the doMoreThings() method.

CodePudding user response:

Try to use the @SpyBean annotation instead of @MockBean for the dependencies of MyService. The @SpyBean annotation will create a spy of the bean and inject it into the application context, while still allowing you to use Mockito to stub the methods of the spy.

For example:

@Import({MyService.class})
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
    @Autowired
    private MyService myService;
    
    @SpyBean
    private BeanOne beanOne;

    @SpyBean
    private BeanTwo beanTwo;

    @Test
    public void test(){
        myService.serviceAction();
        verify(beanTwo,times(3)).doSomething();
    }
}

This should allow you to test the MyService class with its dependencies, while still using the actual MyService instance from the application context.

CodePudding user response:

@Import is used to load configurations you should use @SpringApplicationConfiguration or even better the newer @SpringBootTest annotation. @SpringApplicationConfiguration is currently deprecated by the way.

example:

@SpringBootTest(classes = {MyService.class}) // or @SpringApplicationConfiguration with the same paramters
@RunWith(SpringRunner.class)
public class MyServiceIntegrationTest{
    @Autowired
    private MyService myService;
    
    @MockBean
    private BeanOne beanOne;

    @MockBean
    private BeanTwo beanTwo;

    @Test
    public void test(){
        myService.serviceAction();
        verify(beanTwo,times(3)).doSomething();
    }
}

check:

  • Related