Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@Transcational test class affects how transactional service layer works

Tags:

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 !

like image 438
Arian Avatar asked Aug 23 '17 05:08

Arian


People also ask

What is transactional testing?

TRANSACTION TESTING. Transaction testing generally refers to the testing of individual loans and is also known as account testing, account sampling, or transaction-level testing.

What is @transactional in Junit?

So what does @Transactional mean if you annotate your test suite with it? Well it means that every test method in your suite is surrounded by an overarching Spring transaction. This transaction will be rolled back at the end of the test method regardless of it's outcome.

What annotation is used on an integration test method to run it in a transaction?

@Transaction annotation for integration testing in Spring Boot.


1 Answers

It is rolled back when test is annotated with @Transactional.

  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.

Please post your other tests for more details.

  1. If annotating a test class with @Transactional means there's no control on data persistence, why do people even use it on their tests ?

To prevent filling database with test data.

  1. 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 ?

Where do you check if an item was deleted from the persistence context?

  1. 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.

Every method in the test is wrapped with Spring transaction so data may be not committed until the test ends.

Check for detailed answers:

  • Should my tests be @Transactional?

  • Spring boot test @Transactional not saving

like image 184
Justinas Jakavonis Avatar answered Oct 16 '22 03:10

Justinas Jakavonis