@Transcational test class affects how transactional service layer works

Arian :

I'm writing integration tests for my Spring boot app Rest controller.

When I annotate the test class with @Transactional it doesn't work as expected, and when I remove the annotation it passes fine.

  1. Does using @Transactional on a test class mean absolutely nothing gets written to the db ? My other tests work fine ! They do more or less the same job. They write/update/read but this test tests a delete endpoint.

  2. If annotating a test class with @Transactional means there's no control on data persistence, why do people even use it on their tests ? I injected entity manager to the test class and called flush and clear, it didn't help.

  3. Even if the data is not written to the db, they are persisted, right ? Doesn't calling repository.delete should delete that item from the persistence context ?

  4. The code that doesn't affect the db (delete) is located in the Service layer. It's called from within the Controller that I'm testing, not the test class. I expected it to work regardless of the fact that test class is annotated with @Transacational or not.

Note Service layer is @Transactional

This is in the service layer and is called by the controller. It's not called form within the test.

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

Edit 1

The code for the test that fails:

/*
 * 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("[email protected]", "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);
}

The code for the test that works in the same test class:

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

Create and Delete methods in the 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");
}

The code for the rest controller:

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

Edit 2

I tried deleting User instead of Group and it doesn't work either. In the same method (delete method of the Group Service layer) creating a group works, but deleting does not !

Arian :

After digging around for some time, I found out what the issue was. The User class has a list of Group entities. In the delete method of the Group service layer I had to remove the deleted group from the list of groups a user points to. It is disappointing that the persistent context doesn't throw any exception for it.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=429145&siteId=1