Класс тестирования @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
метод уровня групповой службы) создание группы работает, но удаление этого не происходит!
Ответы
Ответ 1
После долгой работы я выяснил, в чем проблема.
Класс User
имеет список объектов Group
. В методе delete
уровня сервиса Group
мне пришлось удалить удаленную группу из списка групп, на которые указывает пользователь. Неутешительно, что постоянный контекст не создает для него никакого исключения.
Ответ 2
Он откат, когда тест аннотируется с помощью @Transactional
.
- Использует ли использование @Transactional в тестовом классе абсолютно ничего не записывается в db? Мои другие тесты работают нормально! Они выполняют более или менее ту же работу.
Пожалуйста, разместите свои другие тесты для более подробной информации.
- Если аннотирование тестового класса с помощью @Transactional означает отсутствие контроля над сохранением данных, почему люди даже используют его на своих тестах?
Чтобы предотвратить заполнение базы данных тестовыми данными.
- Даже если данные не записываются в db, они сохраняются, правильно? Не вызывает вызов repository.delete, чтобы удалить этот элемент из контекст персистентности?
Где вы проверяете, был ли элемент удален из контекста персистентности?
- Код, который не влияет на db (delete), находится на уровне сервиса. Он вызвал из Контроллера, что я тестирование, а не тестовый класс. Я ожидал, что он будет работать независимо от факт, что тестовый класс аннотируется с @Transacational или нет.
Каждый метод теста завершается транзакцией Spring, поэтому данные могут не выполняться до окончания теста.
Проверьте подробные ответы:
Ответ 3
В более широкой перспективе я думаю, что вы можете использовать неправильный инструмент для работы.
Тесты должны быть изолированы друг от друга, поэтому порядок выполнения тестов не влияет на результаты.
Это достигается в JUnit, например, путем создания нового экземпляра тестового класса для каждого его тестового метода, который должен быть выполнен.
Для тестов интеграции, которые проверяют логику в рамках конкретной транзакции, это можно достичь, начав транзакцию при запуске теста, а затем вернув ее в конце теста. Таким образом, база данных не несет никаких тестовых данных и поэтому готова к использованию в следующем тесте.
Для тестирования контроллера отдыха может потребоваться другой подход. Вероятно, вы запускаете транзакцию где-то внутри этого контроллера, а не до того, как будет задействован код контроллера останова, когда он работает в вашей рабочей среде. У вас могут быть случаи, когда контроллеры вызывают связь с другими системами, чем с db (если это разрешено в тестах вашего контроллера). Или вы можете иметь случаи, когда несколько транзакций выполняются в одном вызове контроллера останова или транзакции, которая использует изолированную транзакцию по умолчанию и так далее. Эти случаи не будут работать со стандартным поведением тестовых случаев @Transactional
.
Итак, вы можете пересмотреть свой подход к тестированию и определить, какова область тестирования каждого набора тестов. Затем, основываясь на этих областях, вы можете определить стратегию определения изоляции тестов в каждой области. Затем для каждого тестового прогона применяется соответствующая стратегия.