尽管使用 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.
我的 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.