JUNIT5 @InjectMocks throws NullPointerException

Viewed 11893

This is my first project using TDD and JUNIT 5. I am using latest Springboot for my project.

I have noticed that when I have dependencies coming from springboot, they are not getting injected during test phase when using @InjectMocks annotation. I am getting NullPointerException for authenticationManager dependency. However, the test passes when the service method only uses a repository dependency created using springboot JPA for entity class.

Below is the service class and the corresponding test class. UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

@Autowired
AuthenticationManager authenticationManager;

@Autowired
UserRepository userRepository;

@Autowired
PasswordEncoder passwordEncoder;

@Autowired
UserDetailsService userDetailsService;

@Autowired
JWTUtil jwtUtil;

@Override
@Transactional
public AuthenticationResponseDTO login(AuthenticationRequestDTO authenticationRequestDTO) {
    try {
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(authenticationRequestDTO.getUserName(), authenticationRequestDTO.getPassword()));

        UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequestDTO.getUserName());

        return new AuthenticationResponseDTO(userDetails.getUsername(), jwtUtil.generateToken(userDetails));
    }
    catch (BadCredentialsException e) {
        throw e;
    }
}

UserServiceImplTest.java

@InjectMocks
private UserServiceImpl userServiceImpl;

@Mock
private UserRepository userRepository;

private User userMock;

private AuthenticationRequestDTO authenticationRequestDTO;

@BeforeEach
void init(){
    MockitoAnnotations.initMocks(this);
}

@BeforeEach
void setupUser(){
    userMock = new User();
    userMock.setUserName("sd");
    userMock.setPassword("sd");

    authenticationRequestDTO = new AuthenticationRequestDTO();
    authenticationRequestDTO.setUserName("sd");
    authenticationRequestDTO.setPassword("sd");
}

@Test
void testUserIsPresentOrNot(){

    Mockito.when( userRepository.findByUserName("sd") ).thenReturn(userMock);

    AuthenticationResponseDTO responseDTO = userServiceImpl.login(authenticationRequestDTO);

    assertNotNull(responseDTO);
    assertEquals(userMock.getUserName(), responseDTO.getName(), "user id should be same.");
}

Please let me know if further details are needed from my end.

4 Answers

I am not sure I am 100% following, as match as I can see you are missing some annotations.

If you want to inject Spring beans in a Unit test you need @SpringBootTest/@ExtendWith(SpringExtension.class) (Making it integration test from my point of view).

If you want to use Mockito @InjectMocks and @Mock require @ExtendWith(MockitoExtension.class) above the test class.

And delete the following.

@BeforeEach
void init(){
    MockitoAnnotations.initMocks(this);
}

Mixing both dependency injection with spring and Mockito will be too complicate from my point of view. I would prefer to use only Mockito for unit tests.

Maybe the following article can help.

TL/DR

add the missing mocks for the Spring dependencies, like so:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;

class UserServiceImplSuite
{
    public static final String USER_NAME = "USER_NAME";
    public static final String PASSWORD = "PASSWORD";
    @InjectMocks
    private UserServiceImpl userServiceImpl;

    @Mock
    private UserRepository userRepository;

    @Mock
    private AuthenticationManager authenticationManager;

    @Mock
    private UserDetailsService userDetailsService;

    @Mock
    private JWTUtil jwtUtil;

    private User userMock;

    private UserDetails userDetailsMock;

    private AuthenticationRequestDTO authenticationRequestDTO;

    @BeforeEach
    void init()
    {
        MockitoAnnotations.initMocks( this );
    }

    @BeforeEach
    void setupUser()
    {
        userMock = new User();
        userMock.setUserName( USER_NAME );
        userMock.setPassword( PASSWORD );

        userDetailsMock = new UserDetails();
        userDetailsMock.setUsername( USER_NAME );
        userDetailsMock.setPassword( PASSWORD );

        authenticationRequestDTO = new AuthenticationRequestDTO();
        authenticationRequestDTO.setUserName( USER_NAME );
        authenticationRequestDTO.setPassword( PASSWORD );
    }

    @Test
    void testUserIsPresentOrNot()
    {

        when( userRepository.findByUserName( USER_NAME ) ).thenReturn( userMock );
        when( userDetailsService.loadUserByUsername( USER_NAME ) )
                .thenReturn( userDetailsMock );
        when( jwtUtil.generateToken( userDetailsMock ) )
                .thenReturn( PASSWORD );

        AuthenticationResponseDTO responseDTO = userServiceImpl.login( authenticationRequestDTO );

        assertNotNull( responseDTO );
        assertEquals( userMock.getUserName(), responseDTO.getName(), "user id should be same." );
    }
}

Breakdown

While running tests, Spring is not active, unless you specifically state it to be using annotations. However, good unit tests generally do not need Spring to run. Since Spring is no longer managing our dependencies (which, again, is good), we now need to take over using Mockito; manually mocking and initializing them, yielding the unit test suite above.

So now, each time the tested class calls upon a dependency, we provide it a mock. This changes the test; We are no longer testing 'does it produce the expected value', but instead check 'does it call the dependencies in order, processing their results correctly' which is much more abstract.

This brings to light te largest problem with the tested class: overcoupling. It is trying to do many, many things, which makes our tests convoluted and brittle. Reducing this will improve both production and test code quality.

Good luck!

In Junit 5 simply annotate test class with @ExtendWith(SpringExtension.class) to use @InjectMocks and make sure your import should be from import org.junit.jupiter.api.Test; like this:

@ExtendWith(SpringExtension.class)
public class TestClass{
    @InjectMocks
    TestInjectMockClass testInjectMockClass;
}

For JUnit 5, you will to use the Mockito extension by annotating @ExtendWith(MockitoExtension.class) on your Test class.

For some reason @InjectMocks did not inject the Mocks until I autowired the beans defined in the Constructor myself in @BeforeEach. Say you want to inject the UserRepository Mock, the you can include it in the constructor.

@BeforeEach
void init(){
    userService = new UserServiceImpl(userRepository);
}
Related