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

Mocking repository function leads to null pointer exception despite using when and thenReturn

我的 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);
  }
}

我对 create 的服务测试如下所示:

@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 classes 的方式有问题。在测试 class 中模拟 classes 时需要做哪些不同的事情?

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

@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());
  }

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

@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;```

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

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

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

将此留给其他人,他们也浪费了一个下午的时间试图让存储库达到 return 他们配置的内容。最终我不得不做的事情不是用 @Mock 注释存储库,而是必须使用 @MockBean 注释才能使 Mockito/Spring 组合正常工作。

根据 @MockBean 的 javadoc:

When @MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field.