Search code examples
spring-bootjunitmockingmockito

Why I geeting java.lang.NullPointerException when a MockBean is executed?


I have the following service class (Spring Boot):

@Service
public class  MyService {
        
    @Autowired
    private DependedService dependedService;
            
    @Transactional
    public boolean saveMyDetails(Integer id, List<> myList) {
        
        dependedService.saveSomething(id, myList);
        ...
        return true;
    }

I've implemented a test method to assert that the service method return true. please note that the requirement is to ignore the call to the depended service:

@RunWith(SpringRunner.class)
@SpringBootTest
public class  MyServiceTests {          
        
    @Mock
    private MyService myService = new MyService ();
    
    @MockBean
    private DependedService dependedService;
        
    @BeforeEach
    public void init() {
        ...     
        ...     
    }
    
    @Test
    public void testSaveMyDetails() {
        ...
        ... 
        doNothing().when(dependedService).saveSomething(Mockito.anyInt(), Mockito.anyList());                           
        when(myService.saveMyDetails(someId, someList)).thenCallRealMethod();       
        assertEquals(true, myService.saveMyDetails(someId, someList));
    }

When running the test it seems that the depended service is not initialized correctly and I'm not sure what should it annotated with in the test class (in the service is a Autowired) the exception is thrown at the service layer:

java.lang.NullPointerException: Cannot invoke ... because "<local4>.dependedService" is null

Solution

  • You should not instantiate MyService on your own since you are using Spring framework for testing. You are not allowing the framework to do its work.

    There are at least two options:

    1. In case if MyService bean should be a bean (not a mock), just remove the instantiation and add @Autowired annotation to let Spring inject the mocked bean into the service. In that case, since your MyService bean is a bean that you are testing and filling with mocks, MyService bean will not be a mock, so you don't have to call thenCallRealMethod.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    class MyServiceTest {
    
        @Autowired
        private MyService myService;
    
        @MockBean
        private DependedService dependedService;
    
        @BeforeEach
        public void init() {
        }
    
        @Test
        void testSaveMyDetails() {
            doNothing().when(dependedService).saveSomething(Mockito.anyInt(), Mockito.anyList());
            myService.saveMyDetails(someId, someList);
            assertTrue(myService.saveMyDetails(someId, someList));
        }
    }
    
    
    1. In case if you want to manage the service bean behaviour, you can use @SpyBean annotation. Just remove the instantiation and add the annotation.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    class MyServiceTest {
    
        @SpyBean
        private MyService myService;
    
        @MockBean
        private DependedService dependedService;
    
        @BeforeEach
        public void init() {
           // I added this just to show that you have an 
           // ability to manage the service bean behaviour.
           // You can remove this.
           when(myService.saveMyDetails(someOtherId, someOtherList))
               .thenThrow(new IllegalArgumentException());
        }
    
        @Test
        void testSaveMyDetails() {
            doNothing().when(dependedService).saveSomething(Mockito.anyInt(), Mockito.anyList());
            myService.saveMyDetails(someId, someList);
            assertTrue(myService.saveMyDetails(someId, someList));
        }
    }