Search code examples
javaspring-bootjunitmockitocode-coverage

Throwing nullpointerexception for nested bean that autowired in Junit mockito test case


I have Junit test case for service layer, from service method logic calling some other beans with autowired and in the unit test case I used that bean as Autowired beacuse that also should cover as part of code coverage.

class CustomerServiceTest{

   @Autowired(or InjectMocks)
   private EmployeeService employeeService;

   @Autowired
   private CustomerService customerService;

   @Test
   public void testcustomerIdNotNull() {
       //mocking some repository call
       Customer customer = customerService.getCustomerDetails();
       //all other test case code
   }
}

 //Customer Service code
 @Service
 public class CustomerService{

 @Autowired
 EmployeeService employeeService;

 public Customer getCustomerDetails() {
       //some code
      employeeService.getEmployee();   /// **Here it was throwing nullpointerexception** 
    //some logic and complete code
 }
}

I tried Autowired with Spy but didn't work.


Solution

  • Your setup is weird. And your comment suggests that you think that @Autowired and @InjectMocks are interchangable. They are absolutely not.

    The first question you have to ask yourself is if you want to run a Spring Boot test (loading the application context) or if you want to execute a test that uses regular Java without Spring.

    If you need a Spring Boot test:

    @SpringBootTest
    class CustomerServiceTest{
    
      @MockBean // register a mock version of your repository bean
      private YourRepository yourRepository;
    
      @Autowired
      private CustomerService customerService;
    
      @Test
      public void testcustomerIdNotNull() {
        // mocking some repository call ...
        Customer customer = customerService.getCustomerDetails();
        // all other test case code
      }
    }
    

    If you don't need Spring's application context and want to mock your dependencies:

    @ExtendWith(MockitoExtension.class)
    class CustomerServiceTest{
    
      @Mock // creates a mock instance
      private YourRepository yourRepository;
    
      @InjectMocks // creates a real instance and injects mocks
      private CustomerService customerService;
    
      @Test
      public void testcustomerIdNotNull() {
        // mocking some repository call ...
        Customer customer = customerService.getCustomerDetails();
        // all other test case code
      }
    }
    

    @Mock and @InjectMocks of Mockito go together as do @MockBean and @Autowired of Spring Boot. You must not mix them.

    IIRC, @InjectMocks should be able to inject fields, but if it doesn't move away from the discouraged method of field injection and migrate your class to constructor injection:

    @Service
    public class CustomerService {
    
      private final EmployeeService employeeService;
    
      @Autowired
      public CustomerService(final EmployeeService employeeService) {
        this.employeeService = employeeService;
      }
    
      public Customer getCustomerDetails() {
        //some code
        employeeService.getEmployee();
        //some logic and complete code
      }
    }
    

    This has the added benefit of allowing to mark your fields as final.