java - studio - La clase de prueba @Transcational afecta cómo funciona la capa de servicio transaccional
capa logica de una red (3)
Desde una perspectiva más amplia, creo que puede estar usando la herramienta incorrecta para el trabajo.
Las pruebas deben estar aisladas unas de otras para que el orden de las pruebas no tenga un impacto en los resultados.
Esto se logra en JUnit, por ejemplo, creando una nueva instancia de la clase de prueba para cada método de prueba que se va a ejecutar.
Para las pruebas de integración que están probando la lógica dentro de una transacción específica, esto se puede lograr al iniciar la transacción en el inicio de la prueba y luego revertirla al final de la prueba. Por lo tanto, la base de datos no incluye datos específicos de prueba y, por lo tanto, está lista para ser utilizada por la siguiente prueba.
Para probar un controlador de descanso, posiblemente se requiera otro enfoque. Es probable que inicie una transacción en algún lugar dentro de ese controlador y no antes de que se invoque el código del controlador de descanso real cuando se ejecuta en su entorno de producción. Es posible que haya casos en los que los controladores causen la comunicación con otros sistemas que no sean la base de datos (si están permitidos en las pruebas de su controlador). O puede tener casos en los que se realicen transacciones múltiples dentro de la misma invocación del controlador de descanso o una transacción que use un aislamiento de transacción no predeterminado y así sucesivamente. Estos casos no funcionarán con el comportamiento predeterminado de los casos de prueba de @Transactional
.
Por lo tanto, es posible que desee reconsiderar su enfoque de prueba y definir cuál es el alcance de la prueba de cada conjunto de pruebas. Luego, en función de estos ámbitos, puede definir una estrategia sobre cómo lograr el aislamiento de las pruebas en cada ámbito. Luego, para cada ejecución de prueba aplique la estrategia apropiada.
Estoy escribiendo pruebas de integración para mi controlador de descanso de la aplicación Spring Boot.
Cuando @Transactional
clase de prueba con @Transactional
, no funcionó como se esperaba, y cuando @Transactional
la anotación, pasa bien.
¿
@Transactional
en una clase de prueba significa que nada se escribe en la base de datos? ¡Mis otras pruebas funcionan bien! Hacen más o menos el mismo trabajo. Escriben / actualizan / leen, pero esta prueba prueba un punto final de eliminación.Si la anotación de una clase de prueba con @Transactional significa que no hay control sobre la persistencia de los datos, ¿por qué la gente lo usa en sus pruebas? Inyecté el administrador de la entidad a la clase de prueba y llamé al
flush
yclear
, no ayudó.Incluso si los datos no se escriben en la base de datos, se persisten, ¿verdad? ¿El hecho de llamar a
repository.delete
debería eliminar ese elemento del contexto de persistencia?El código que no afecta a la db (eliminar) se encuentra en la capa de servicio. Se llama desde el controlador que estoy probando, no la clase de prueba. Esperaba que funcionara independientemente del hecho de que la clase de prueba esté anotada con
@Transacational
o no.
Nota capa de servicio es @Transactional
Esto está en la capa de servicio y es llamado por el controlador. No se llama forma dentro de la prueba.
public void delete(long groupId, String username) {
Group group = this.loadById(groupId);
User user = userService.loadByUsername(username);
groupRepository.delete(groupId);
}
Editar 1
El código para la prueba que falla:
/*
* 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);
}
El código para la prueba que funciona en la misma clase de prueba:
@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());
}
Crear y eliminar métodos en el 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");
}
El código para el resto del controlador:
/*
* 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());
}
Editar 2
Intenté eliminar User
lugar de Group
y tampoco funciona. En el mismo método (método de delete
de la capa de servicio de grupo), la creación de un grupo funciona, pero la eliminación no.
Después de cavar alrededor por un tiempo, descubrí cuál era el problema. La clase de User
tiene una lista de entidades de Group
. En el método de delete
de la capa de servicio de Group
tuve que eliminar el grupo eliminado de la lista de grupos a los que apunta un usuario. Es decepcionante que el contexto persistente no arroje ninguna excepción para ello.
Se @Transactional
cuando la prueba se anota con @Transactional
.
- ¿Usar @Transactional en una clase de prueba significa que nada se escribe en la base de datos? ¡Mis otras pruebas funcionan bien! Hacen más o menos el mismo trabajo.
Por favor, publique sus otras pruebas para más detalles.
- Si la anotación de una clase de prueba con @Transactional significa que no hay control sobre la persistencia de los datos, ¿por qué la gente lo usa en sus pruebas?
Para evitar el llenado de la base de datos con datos de prueba.
- Incluso si los datos no se escriben en la base de datos, se persisten, ¿verdad? ¿El hecho de llamar a repository.delete no debería eliminar ese elemento del contexto de persistencia?
¿Dónde verifica si un elemento se eliminó del contexto de persistencia?
- El código que no afecta a la db (eliminar) se encuentra en la capa de servicio. Se llama desde el controlador que estoy probando, no la clase de prueba. Esperaba que funcionara independientemente del hecho de que la clase de prueba esté anotada con @Transacational o no.
Todos los métodos de la prueba se envuelven con la transacción Spring, por lo que es posible que los datos no se confirmen hasta que finalice la prueba.
Compruebe las respuestas detalladas: