Подтвердить что ты не робот

Класс тестирования @Transcational влияет на то, как работает уровень обслуживания транзакций

Я пишу интеграционные тесты для моего контроллера загрузки Spring.

Когда я комментирую тестовый класс с помощью @Transactional, он не работает должным образом, и когда я удаляю аннотацию, он проходит отлично.

  • Используется ли @Transactional для тестового класса абсолютно ничего не записывается в db? Мои другие тесты работают нормально! Они выполняют более или менее ту же работу. Они пишут/обновляют/читают, но это тест тестирует конечную точку удаления.

  • Если аннотирование тестового класса с @Transactional означает, что нет контроля над сохранением данных, почему люди даже используют его в своих тестах? Я ввел диспетчер объектов в тестовый класс и назвал flush и clear, это не помогло.

  • Даже если данные не записаны в db, они сохраняются, правильно? Не вызывает ли repository.delete вызов этого элемента из контекста персистентности?

  • Код, который не влияет на db (delete), находится на уровне сервиса. Он вызвал внутри Контроллера, что я тестирую, а не тестовый класс. Я ожидал, что он будет работать независимо от того, что тестовый класс аннотируется с помощью @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("[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);
}

Код для теста, который работает в одном и том же классе:

@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, и он тоже не работает. В том же методе (delete метод уровня групповой службы) создание группы работает, но удаление этого не происходит!

4b9b3361

Ответ 1

После долгой работы я выяснил, в чем проблема. Класс User имеет список объектов Group. В методе delete уровня сервиса Group мне пришлось удалить удаленную группу из списка групп, на которые указывает пользователь. Неутешительно, что постоянный контекст не создает для него никакого исключения.

Ответ 2

Он откат, когда тест аннотируется с помощью @Transactional.

  • Использует ли использование @Transactional в тестовом классе абсолютно ничего не записывается в db? Мои другие тесты работают нормально! Они выполняют более или менее ту же работу.

Пожалуйста, разместите свои другие тесты для более подробной информации.

  1. Если аннотирование тестового класса с помощью @Transactional означает отсутствие контроля над сохранением данных, почему люди даже используют его на своих тестах?

Чтобы предотвратить заполнение базы данных тестовыми данными.

  1. Даже если данные не записываются в db, они сохраняются, правильно? Не вызывает вызов repository.delete, чтобы удалить этот элемент из контекст персистентности?

Где вы проверяете, был ли элемент удален из контекста персистентности?

  1. Код, который не влияет на db (delete), находится на уровне сервиса. Он вызвал из Контроллера, что я тестирование, а не тестовый класс. Я ожидал, что он будет работать независимо от факт, что тестовый класс аннотируется с @Transacational или нет.

Каждый метод теста завершается транзакцией Spring, поэтому данные могут не выполняться до окончания теста.

Проверьте подробные ответы:

Ответ 3

В более широкой перспективе я думаю, что вы можете использовать неправильный инструмент для работы.

Тесты должны быть изолированы друг от друга, поэтому порядок выполнения тестов не влияет на результаты.

Это достигается в JUnit, например, путем создания нового экземпляра тестового класса для каждого его тестового метода, который должен быть выполнен.

Для тестов интеграции, которые проверяют логику в рамках конкретной транзакции, это можно достичь, начав транзакцию при запуске теста, а затем вернув ее в конце теста. Таким образом, база данных не несет никаких тестовых данных и поэтому готова к использованию в следующем тесте.

Для тестирования контроллера отдыха может потребоваться другой подход. Вероятно, вы запускаете транзакцию где-то внутри этого контроллера, а не до того, как будет задействован код контроллера останова, когда он работает в вашей рабочей среде. У вас могут быть случаи, когда контроллеры вызывают связь с другими системами, чем с db (если это разрешено в тестах вашего контроллера). Или вы можете иметь случаи, когда несколько транзакций выполняются в одном вызове контроллера останова или транзакции, которая использует изолированную транзакцию по умолчанию и так далее. Эти случаи не будут работать со стандартным поведением тестовых случаев @Transactional.

Итак, вы можете пересмотреть свой подход к тестированию и определить, какова область тестирования каждого набора тестов. Затем, основываясь на этих областях, вы можете определить стратегию определения изоляции тестов в каждой области. Затем для каждого тестового прогона применяется соответствующая стратегия.