【问题标题】:@Transcational test class affects how transactional service layer works@Transcational 测试类影响事务服务层的工作方式
【发布时间】:2017-08-23 05:14:08
【问题描述】:

我正在为我的 Spring Boot 应用程序 Rest 控制器编写集成测试。

当我用@Transactional 注释测试类时,它没有按预期工作,当我删除注释时它通过了。

  1. 在测试类上使用@Transactional 是否意味着绝对 什么都没有写入数据库?我的其他测试工作正常! 他们或多或少做同样的工作。他们写/更新/读但是这个 test 测试删除端点。

  2. 如果使用 @Transactional 注释测试类意味着无法控制数据持久性,为什么人们甚至在测试中使用它?我将实体管理器注入测试类并调用flushclear,它没有帮助。

  3. 即使数据没有写入数据库,它们也会被持久化,对吧?不调用repository.delete 应该从持久性上下文中删除该项目吗?

  4. 不影响db(删除)的代码位于Service层。它是从我正在测试的控制器中调用的,而不是测试类。无论测试类是否用@Transacational 注释,我都希望它能够工作。

注意服务层为@Transactional

这是在服务层,由控制器调用。在测试中不叫表单。

public void delete(long groupId, String username) {
    Group group = this.loadById(groupId);
    User user = userService.loadByUsername(username);
    groupRepository.delete(groupId);
}

编辑 1

测试失败的代码:

/*
 * Deleting a group shouldn't delete the members of that group
 */
@Test
public void testDeleteGroupWithMembers() throws Exception {
    Principal mockPrincipal = Mockito.mock(Principal.class);
    Mockito.when(mockPrincipal.getName()).thenReturn(DUMMY_USERNAME);

    User admin = userTestingUtil.createUser(DUMMY_USERNAME, DUMMY_USER_NAME, null, null);
    Group group = groupTestingUtil.createGroup(DUMMY_GROUP_NAME, DUMMY_GROUP_DESCRIPTION, DUMMY_IMAGE_ID, admin);

    User member = userTestingUtil.createUser("test1@test.test", "testUser1" , null, null);
    group.addMember(member);

    RequestBuilder requestBuilder = MockMvcRequestBuilders
            .delete(GROUP_ENDPOINT_URL + group.getId())
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .principal(mockPrincipal);

    MvcResult result = mockMvc.perform(requestBuilder).andReturn();
    MockHttpServletResponse response = result.getResponse();
    int status = response.getStatus();
    String content = response.getContentAsString();
    Assert.assertEquals("wrong response status", 200, status);
    Assert.assertEquals("wrong response content", "", content);
    //This test fails, as the group is not yet deleted from the repo
    Assert.assertEquals("there should be no group left", 0, Lists.newArrayList(groupRepository.findAll()).size());
    Assert.assertEquals("wrong number of users exist", 2, Lists.newArrayList(userRepository.findAll()).size());
    Assert.assertTrue("admin shouldn't get deleted when deleting a group", userRepository.findById(admin.getId()) != null);
    Assert.assertTrue("group members shouldn't get deleted when deleting a group", userRepository.findById(member.getId()) != null);
}

在同一测试类中工作的测试代码:

@Test
public void testCreateGroup() throws Exception {
    Principal mockPrincipal = Mockito.mock(Principal.class);
    Mockito.when(mockPrincipal.getName()).thenReturn(DUMMY_USERNAME);

    User user = userTestingUtil.createUser(DUMMY_USERNAME, DUMMY_USER_NAME, null, null);

    JSONObject jo = new JSONObject();
    jo.put(NAME_FIELD_NAME, DUMMY_GROUP_NAME);
    jo.put(DESCRIPTION_FIELD_NAME, DUMMY_GROUP_DESCRIPTION);
    jo.put(IMAGE_FIELD_NAME, DUMMY_IMAGE);
    String testGroupJson = jo.toString();

    RequestBuilder requestBuilder = MockMvcRequestBuilders
            .post(GROUP_ENDPOINT_URL).content(testGroupJson)
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .principal(mockPrincipal);

    MvcResult result = mockMvc.perform(requestBuilder).andReturn();
    MockHttpServletResponse response = result.getResponse();
    int status = response.getStatus();
    String content = response.getContentAsString();

    List<Group> createdGroups = Lists.newArrayList(groupRepository.findAll());
    Group createdGroup = createdGroups.get(0);

    Assert.assertEquals("wrong response status", 200, status);
    Assert.assertEquals("wrong response content", "", content);
    Assert.assertEquals("wrong number of groups created", 1, createdGroups.size());
    Assert.assertEquals("wrong group name", DUMMY_GROUP_NAME, createdGroup.getName());
    Assert.assertEquals("wrong group description", DUMMY_GROUP_DESCRIPTION, createdGroup.getDescription());
    Assert.assertEquals("wrong admin is assigned to the group", user.getId(), createdGroup.getAdmin().getId());
    List<Group> groups = userTestingUtil.getOwnedGroups(user.getId());
    Assert.assertEquals("wrong number of groups created for the admin", 1, groups.size());
    Assert.assertEquals("wrong group is assigned to the admin", user.getOwnedGroups().get(0).getId(), createdGroup.getAdmin().getId());
    Assert.assertTrue("image file was not created", CommonUtils.getImageFile(createdGroup.getImageId()).exists());
}

GroupService 中的创建和删除方法:

public void create(String groupName, String description, String image, String username) throws IOException {
    User user = userService.loadByUsername(username);
    Group group = new Group();
    group.setAdmin(user);
    group.setName(groupName);
    group.setDescription(description);
    String imageId = CommonUtils.decodeBase64AndSaveImage(image);
    if (imageId != null) {
        group.setImageId(imageId);
    }
    user.addOwnedGroup(group);
    groupRepository.save(group);
    logger.debug("Group with name " + group.getName() + " and id " + group.getId() + " was created");
}

public void delete(long groupId, String username) {
    Group group = this.loadById(groupId);
    User user = userService.loadByUsername(username);
    validateAdminAccessToGroup(group, user);
    groupRepository.delete(groupId);
    logger.debug("Group with id " + groupId + " was deleted");
}

其余控制器的代码:

/*
 * Create a group
 */
@RequestMapping(path = "", method = RequestMethod.POST)
public void create(@RequestBody PostGroupDto groupDto, Principal principal, BindingResult result) throws IOException {
    createGroupDtoValidator.validate(groupDto, result);
    if (result.hasErrors()) {
        throw new ValidationException(result.getFieldError().getCode());
    }
    groupService.create(groupDto.getName(), groupDto.getDescription(), groupDto.getImage(), principal.getName());
}

/*
 * Delete a group
 */
@RequestMapping(path = "/{groupId}", method = RequestMethod.DELETE)
public void delete(@PathVariable long groupId, Principal principal) {
    groupService.delete(groupId, principal.getName());
}

编辑 2

我尝试删除User 而不是Group,但它也不起作用。使用相同的方法(Group Service 层的delete 方法)创建组有效,但删除无效!

【问题讨论】:

    标签: java hibernate


    【解决方案1】:

    当测试用@Transactional注解时回滚。

    1. 在测试类上使用@Transactional 是否意味着绝对没有任何东西写入数据库?我的其他测试工作正常!他们或多或少做着相同的工作。

    请发布您的其他测试以了解更多详细信息。

    1. 如果使用 @Transactional 注释测试类意味着无法控制数据持久性,为什么人们甚至在测试中使用它 ?

    防止用测试数据填充数据库。

    1. 即使数据没有写入数据库,它们也会被持久化,对吗?不调用 repository.delete 应该从中删除该项目 持久化上下文?

    您在哪里检查项目是否已从持久性上下文中删除?

    1. 不影响db(删除)的代码位于Service层。它是从我的控制器内部调用的 测试,而不是测试类。我希望它能够工作,不管 测试类是否使用@Transacational 注释的事实。

    测试中的每个方法都使用 Spring 事务进行包装,因此在测试结束之前可能不会提交数据。

    查看详细答案:

    【讨论】:

    • 我知道它已经回滚了,但是它是在测试完成运行后回滚的。不在中间。否则,任何带有 @Transactional 注释的测试都无法正常工作。
    • 我在问题中添加了更多细节。
    • 可能是@Transactional测试也没有提交数据/命中DB。
    • 我用测试(事务性)、服务层(事务性)和控制器(非事务性)的代码更新了帖子
    • 我对失败的 Assert 发表了评论。
    【解决方案2】:

    经过一段时间的挖掘,我发现了问题所在。 User 类有一个Group 实体列表。在Group 服务层的delete 方法中,我必须从用户指向的组列表中删除已删除的组。令人失望的是,持久化上下文没有为它抛出任何异常。

    【讨论】:

    • 坦率地说,您建议持久化上下文应该抛出异常,这表明您还没有完全理解它是如何工作的。通常,您不会从User 中删除Group,除非在调用删除方法之前事务已经启动(并且加载了使用)。因为,如果User已经加载,最好保持双向关系与数据库同步,否则调用者将不得不调用em.refresh(user)才能看到该组不再在User's列表中(假设正确的级联)。
    【解决方案3】:

    从更广泛的角度来看,我认为您可能使用了错误的工具来完成这项工作。

    测试需要相互隔离,因此测试运行的顺序不会影响结果。

    这是在 JUnit 中实现的,例如,通过为要执行的每个测试方法创建测试类的新实例。

    对于在特定事务中测试逻辑的集成测试,这可以通过在测试开始时启动事务然后在测试结束时回滚来实现。所以数据库不携带任何测试特定的数据,因此可以在下一个测试中使用。

    为了测试一个休息控制器,可能需要另一种方法。您可能在该控制器中的某处开始事务,而不是在实际的休息控制器代码在您的生产环境中运行时被调用之前。您可能会遇到控制器导致与数据库以外的其他系统通信的情况(如果这些在您的控制器测试中是允许的)。或者,您可能会遇到多个事务在同一个 rest 控制器调用或使用非默认事务隔离的事务等中完成的情况。这些用例不适用于@Transactional 测试用例的默认行为。

    因此,您可能需要重新考虑您的测试方法并定义每组测试的测试范围。然后基于这些范围,您可以定义如何实现每个范围内的测试隔离的策略。然后为每次测试运行应用适当的策略。

    【讨论】:

      猜你喜欢
      • 2011-11-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-23
      • 1970-01-01
      • 1970-01-01
      • 2020-03-17
      • 2013-10-04
      • 1970-01-01
      相关资源
      最近更新 更多