【问题标题】:JUNIT5 @InjectMocks throws NullPointerExceptionJUNIT5 @InjectMocks 抛出 NullPointerException
【发布时间】:2020-08-25 20:09:07
【问题描述】:

这是我第一个使用 TDD 和 JUNIT 5 的项目。我正在为我的项目使用最新的 Springboot。

我注意到,当我有来自 springboot 的依赖项时,在使用 @InjectMocks 注释时,它们不会在测试阶段被注入。我正在为 authenticationManager 依赖项获取 NullPointerException。但是,当服务方法仅使用使用 springboot JPA 为实体类创建的存储库依赖项时,测试通过。

下面是服务类和对应的测试类。 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.");
}

如果我需要更多详细信息,请告诉我。

【问题讨论】:

  • @Test should be imported from org.junit.jupiter.api.Test not org.junit.Test , which can also cause @InjectMock initialization as null

标签: spring-boot spring-security tdd junit5


【解决方案1】:

我不确定我是否 100% 关注,因为我可以看到您缺少一些注释。

如果你想在单元测试中注入 Spring bean,你需要 @SpringBootTest/@ExtendWith(SpringExtension.class)(从我的角度来看它是集成测试)。

如果你想使用 Mockito @InjectMocks@Mock 要求 @ExtendWith(MockitoExtension.class) 高于测试类。

并删除以下内容。

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

在我看来,将依赖注入与 spring 和 Mockito 混合使用将过于复杂。我宁愿只使用 Mockito 进行单元测试。

也许following 文章可以提供帮助。

【讨论】:

  • 好吧,就我而言,我什至不得不删除@SpringBootTest,因为它使@InjectMocks无效
【解决方案2】:

TL/DR

为 Spring 依赖项添加缺少的模拟,如下所示:

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." );
    }
}

故障

在运行测试时,Spring 是不活动的,除非您明确声明它使用注解。但是,好的单元测试通常不需要 Spring 来运行。 由于 Spring 不再管理我们的依赖项(这同样很好),我们现在需要使用 Mockito 来接管;手动模拟和初始化它们,产生上面的单元测试套件。

所以现在,每次被测试的类调用依赖项时,我们都会为其提供一个模拟。这改变了测试;我们不再测试“它是否产生预期值”,而是检查“它是否按顺序调用依赖项,正确处理它们的结果”,这更加抽象。

这暴露了测试类的最大问题:过度耦合。它试图做很多很多事情,这使我们的测试变得复杂而脆弱。减少这一点将提高生产和测试代码的质量。

祝你好运!

【讨论】:

    【解决方案3】:

    在 Junit 5 中,只需使用 @ExtendWith(SpringExtension.class) 注释测试类以使用 @InjectMocks 并确保您的导入应该来自 import org.junit.jupiter.api.Test;,如下所示:

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

    【讨论】:

      【解决方案4】:

      对于 JUnit 5,您将通过在 Test 类上注释 @ExtendWith(MockitoExtension.class) 来使用 Mockito 扩展。

      由于某种原因,@InjectMocks 没有注入 Mocks,直到我自己在 @BeforeEach 中自动装配了构造函数中定义的 bean。 假设您要注入 UserRepository Mock,您可以将其包含在构造函数中。

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

      【讨论】:

        猜你喜欢
        • 2017-08-11
        • 2013-09-07
        • 2013-06-18
        • 2016-07-07
        • 2014-10-02
        • 2012-05-21
        • 2014-08-15
        • 2013-08-06
        • 2019-09-21
        相关资源
        最近更新 更多