尽管使用了when和thenReturn,但模拟存储库函数会导致空指针异常

Posted

技术标签:

【中文标题】尽管使用了when和thenReturn,但模拟存储库函数会导致空指针异常【英文标题】:Mocking repository function leads to null pointer exception despite using when and thenReturn 【发布时间】:2021-02-07 13:53:29 【问题描述】:

我的ServiceImpl 看起来像这样:

@Service
public class GuildUsersServiceImpl implements GuildUsersService 

  @Autowired
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  @Autowired
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) 
    this.guildUsersRepository = guildUsersRepository;
  

  public GuildUsers create(GuildUsers guildUser) 
    return this.guildUsersRepository.save(guildUser);
  

我对@9​​87654323@ 的服务测试如下所示:

@RunWith(SpringRunner.class)
public class GuildUsersServiceTest 
  
  @Mock private GuildUsersRepository guildUsersRepository;
  
  @Mock private UserService userService;

  @Mock private GuildService guildService;
  
  @Mock private GuildUserRoleTypeService guildUserRoleTypeService;

  @InjectMocks private GuildUsersServiceImpl guildUsersService;
  
  @Before
  public void setUp()
      MockitoAnnotations.initMocks(this);
  
  
  @Test
  public void createTest() 
    GuildUsers guildUsers = mockGuildUsers();
    when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
    GuildUsers dbGuildUsers = guildUsersService.create(guildUsers);
    assertEquals(dbGuildUsers.getId(),guildUsers.getId());
  

我正在使用jUnit4。尽管在测试中使用了when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);,但我遇到了带有以下跟踪的空指针异常:

java.lang.NullPointerException: Cannot invoke "com.project.entities.domain.GuildUsers.getId()" because "dbGuildUsers" is null

我怀疑我模拟 @Autowired 类的方式有问题。在 Test 类中模拟类时需要做哪些不同的事情?

同时我希望以下测试用例中的服务也能正常工作:

@Test
  public void createTest1() 
    GuildUsers guildUsers = mockGuildUsers();
    GuildUsersWithoutGuildIdRequestDTO guildUsersWithoutGuildIdRequestDTO = new  GuildUsersWithoutGuildIdRequestDTO();
    guildUsersWithoutGuildIdRequestDTO.setUserId(guildUsers.getUser().getId());
    guildUsersWithoutGuildIdRequestDTO.setGuildUserRoleTypeId(guildUsers.getGuildUserRoleType().getId());
    
    when(guildService.get(guildUsers.getGuild().getId())).thenReturn(Optional.of(guildUsers.getGuild()));
    when(guildRepository.findById(any())).thenReturn(Optional.of(guildUsers.getGuild()));
    when(userService.get(guildUsers.getUser().getId())).thenReturn(Optional.of(guildUsers.getUser()));
    when(guildUsersRepository.findById(guildUsers.getId())).thenReturn(null);
    when(guildUserRoleTypeService.get(guildUsers.getGuildUserRoleType().getId())).thenReturn(Optional.of(guildUsers.getGuildUserRoleType()));
    when(guildUsersRepository.save(any())).thenReturn(guildUsers);
    
    GuildUsersResponseDTO dbGuildUsersResponseDTO = 
        guildUsersService.create(guildUsers.getGuild().getId(),guildUsersWithoutGuildIdRequestDTO);
    assertEquals(dbGuildUsersResponseDTO.getGuildUserRoleType(),guildUsers.getGuildUserRoleType());
  

【问题讨论】:

【参考方案1】:

我不知道您为什么同时进行字段注入和构造函数注入。您应该首先删除其中一个,例如:

@Service
public class GuildUsersServiceImpl implements GuildUsersService 
  
  //@Autowired - removed
  private final GuildUsersRepository guildUsersRepository;

  @Autowired
  private GuildService guildService;

  @Autowired
  private UserService userService;

  @Autowired
  private GuildUserRoleTypeService guildUserRoleTypeService;

  //@Autowired - removed
  public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) 
    this.guildUsersRepository = guildUsersRepository;
  

  public GuildUsers create(GuildUsers guildUser) 
    return this.guildUsersRepository.save(guildUser);
  

或者删除要添加的构造函数:

  @Autowired 
  private final GuildUsersRepository guildUsersRepository;```

【讨论】:

那行不通。在任何一种情况下,我都只传递对象的那个实例,因此不需要调用any() 应该如何模拟和注入其他@Autowired 服务,因为虽然您的回答解决了存储库的问题,但其他自动连接的服务开始抛出 NPE 哦,您对测试进行了修改还是按原样进行了修改? 问题出现在不同的测试用例中。在我的问题中添加相同的内容。 你能具体说一下吗?【参考方案2】:

InjectMock 文档明确指出:“Mockito 将尝试仅通过构造函数注入、setter 注入或属性注入按顺序注入模拟,如下所述。”

在您的情况下,它显然选择了属性注入,因此 guildUserRoleTypeService 保持未初始化。如上所述,混合属性和构造函数注入不是一个好主意。

为了提高 GuildUsersServiceImpl 的总体设计和可测试性,我建议坚持使用构造函数注入。这样您就可以: a) 将所有字段声明为 private final,因此一旦创建对象,它们将始终被设置,避免竞争条件和 NPE。 b) 在测试中使用适当的构造函数而不是 InjectMocks 来初始化 GuildUsersServiceImpl。

【讨论】:

【参考方案3】:

把这个留给其他人,他们也浪费了一个下午试图让一个存储库返回他们配置的内容。最终我不得不做的是,我必须使用 @MockBean 注释来让 Mockito/Spring 组合正常工作,而不是使用 @Mock 注释存储库。

根据@MockBean的javadoc:

当@MockBean 用于字段时,以及在应用程序上下文中注册时,mock 也会被注入到字段中。

【讨论】:

以上是关于尽管使用了when和thenReturn,但模拟存储库函数会导致空指针异常的主要内容,如果未能解决你的问题,请参考以下文章

Mockito 异常 - when() 需要一个参数,该参数必须是模拟上的方法调用

mockito when() 调用如何工作?

Mockito - JUnit + Mockito 单元测试之打桩 when().thenReturn()

Mockito - 存根方法时出现 NullpointerException

case when 和 decode 的比较分析

PowerMock