我正在使用Spring制作REST API,但我无法对其进行单元测试。
我写了一个端点来更新用户组,当我在我的前端创建一个具有重复名称的组时,它确实发送了409冲突(unique = true)。但是,当我进行单元测试时,它没有。我发现在单元测试结束时添加这一行groupRepository.findAll().forEach(g -> System.out.println(g.getName()));
确实会抛出409。
端点:
@Override
public ResponseEntity<Object> update(@ApiParam(value = "form object to add to the store", required = true) @Valid @RequestBody FormGroupDTO group, @ApiParam(value = "Id of the form that needs to be updated", required = true) @PathVariable("groupId") Long groupId, @ApiParam(value = "token to be passed as a header", required = true) @RequestHeader(value = "token", required = true) String token) {
String name = JWTutils.getEmailInToken(token);
if(name == null) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
User user = userRepository.findByEmail(name);
if(user == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
FormGroup groupModel = groupRepository.findByIdAndAdmin(groupId, user);
if(groupModel == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
if(group.getMembers().stream().filter(m -> m.getRole() == UserFormGroupRole.ADMIN).toArray().length == 0){
return new ResponseEntity<>(new ValidationErrorDTO("noAdmin", "MEMBER.NOADMIN"), HttpStatus.BAD_REQUEST);
}
// Get users
groupModel.getUserFormGroups().clear();
for(MemberDTO member : group.getMembers()){
User u = userRepository.findByEmail(member.getEmail());
if(u == null){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTEXIST"), HttpStatus.BAD_REQUEST);
}
if(u.getRole() == UserRole.USER){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTPRIVILEGED"), HttpStatus.BAD_REQUEST);
}
UserFormGroup ufg = userFormGroupRepository.findByUserAndFormGroup(u, groupModel);
if(ufg == null){
groupModel.getUserFormGroups().add(new UserFormGroup(u, groupModel, member.getRole()));
} else{
ufg.setRole(member.getRole());
groupModel.getUserFormGroups().add(ufg);
}
}
groupModel.setName(group.getName());
try{
groupRepository.save(groupModel);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (DataIntegrityViolationException e){
System.out.println(e.getMessage());
System.out.println(e.getClass());
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
}
我的单元测试:
@Test
public void updateGroupNameAlreadyInUse() throws Exception {
groupRepository.save(new FormGroup("newFormGroup", user));
this.mockMvc.perform(put("/groups/" + group.getId())
.header("token", token)
.content(json(new FormGroupDTO("newFormGroup", group.getUserFormGroups().stream().map(ufg -> new MemberDTO(ufg.getUser().getEmail(), ufg.getRole())).collect(Collectors.toList()))))
.contentType(contentType))
.andExpect(status().isConflict());
}
我的CrudRepository的save函数并不总是抛出DataIntegrityViolationException。我刚刚意识到,我的单元测试groupRepository.save(new FormGroup("newFormGroup", user));
的第一行可能在单元测试结束之前没有执行,findAll
函数会触发它。
答案 0 :(得分:1)
简短的故事:在测试中插入后,您需要手动执行flush()
。现在让我们详细讨论。我假设您正在使用ID生成策略,如Sequence或UUID或类似的东西。
有许多事情需要考虑:
SELECT
语句之前和事务提交之前触发。获取每条记录名称的解决方案会发出SELECT
语句 - 这会触发刷新所有挂起的SQL语句。save()
或persist()
保证返回持久对象。这样的对象必须有ID。某些ID生成策略(如Identity
)需要INSERT
语句来生成ID。其他人(如Sequence
,UUID
) - 不要。因此,ORM可以在不插入记录的情况下获取ID(它希望尽可能地延迟某些优化)。因此,在进行与ORM相关的测试时,您必须先手动调用flush()
,然后再对该数据执行任何操作。
当您将事物标记为@Transactional
时,行为是:
我假设您使用@Transactional
标记了您的测试。这意味着它测试谁开始会话和交易。存储库只使用已经打开的存储库。然后因为没有提交 - 没有刷新。然后你使用MockMvc - 在同一个线程中工作。它经过@Transactional
或OSIV
,也发现该事务已经启动。因此,交易被重复使用。
然后它转到你的核心逻辑 - 你正在做一些SELECT
语句,这会刷新当前会话中的挂起的SQL语句。所以你原来的save()
只是脸红了。
现在,在您的逻辑结束时,您再次执行save()
,将INSERT语句放入挂起的SQL查询中。测试完成后,它只会回滚交易,最终INSERT
不会发生。除非..你正在做你提到的SELECT
陈述。
最后 - 不要忘记在ORM相关测试中执行flush()
和clear()
。这些方法存在于Hibernate的Session
或JPA&#39; EntityManager
中。前者可以通过以下方式完成:SessionFactory#getCurrentSession()
。后者可以注入:
@PersistenceContext
EntityManager entityManager;
PS:我没有看到任何标有@Transactional
的生产代码。如果你不这样做,你可能会遇到问题。