2017-08-23 23 views
19

Piszę testy integracji dla mojego kontrolera Rest.@ Testowa klasa testowa wpływa na sposób funkcjonowania warstwy usług transakcyjnych

Po dodaniu adnotacji do klasy testowej z @Transactional nie działa zgodnie z oczekiwaniami, a po usunięciu adnotacji przechodzi poprawnie.

  1. Czy używając @Transactional w klasie testowej znaczy absolutnie nic nie zostanie zapisane w db? Moje inne testy działają dobrze! Wykonują mniej więcej tę samą pracę. Piszą/aktualizują/czytają, ale ten test testuje usuwany punkt końcowy.

  2. Jeśli opisywanie klasy testowej za pomocą @Transactional oznacza, że ​​nie ma kontroli nad utrwalaniem danych, dlaczego ludzie używają go nawet w swoich testach? Wpisałem menedżera podmiotu do klasy testowej i zadzwoniłem pod numer flush i clear, to nie pomogło.

  3. Nawet jeśli dane nie są zapisane w bazie danych, są utrwalone, prawda? Czy wywołanie repository.delete nie powinno usunąć tego elementu z kontekstu utrwalania?

  4. Kod, który nie ma wpływu na db (usuwanie) znajduje się w warstwie Usługi. Jest wywoływany z kontrolera, który testuję, a nie z klasy testowej. Spodziewałem się, że zadziała, niezależnie od faktu, że klasa testowa jest opatrzona komentarzem z @Transacational lub nie.

Uwaga warstwa Service jest @Transactional

Jest w warstwie usług i nazywany jest przez kontroler. Nie jest to tak zwana forma w teście.

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

Edycja 1

Kod dla testu, który kończy się niepowodzeniem:

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

Kod do testu, który działa w tej samej klasie testu:

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

Utwórz i usuń metody w 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"); 
} 

Kod dla kontrolera spoczynku:

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

Edycja 2

Próbowałem usuwanie User zamiast Group i to nie działa. W tej samej metodzie (delete metoda warstwy usługi grupy) tworzenie grupy działa, ale usunięcie nie działa!

Odpowiedz

5

Po wykopaniu jakiegoś czasu, dowiedziałem się, co było problemem. Klasa User zawiera listę jednostek Group. W metodzie obsługi warstwy delete musiałem usunąć usuniętą grupę z listy grup, do której użytkownik wskazuje. Rozczarowuje to, że trwały kontekst nie rzuca na nie żadnego wyjątku.

+0

Być nieco tępym, sugestia, że ​​kontekst utrwalania powinien rzucić wyjątek, wskazuje, że nie w pełni rozumiałeś, jak to działa. Zazwyczaj nie usuniesz 'Group' z' User', chyba że transakcja jest już uruchomiona (i użycie i załadowano) przed wywołaniem metody delete. Ponieważ, jeśli 'User' jest już załadowany, najlepiej jest utrzymywać relacje dwukierunkowe zsynchronizowane z bazą danych, w przeciwnym razie wywołujący będzie musiał wywołać' em.refresh (user) ', aby zobaczyć, że grupa nie jest już w'. Lista użytkowników (zakładając poprawną kaskadę). –

6

Jest wycofywany, gdy test jest opatrzony przypisami z @Transactional.

  1. Czy używając @Transactional w klasie testowej znaczy absolutnie nic nie zostanie zapisane w db? Moje inne testy działają dobrze! Wykonują mniej więcej taką samą pracę.

Proszę zamieścić swoje inne testy, aby uzyskać więcej informacji.

  1. W przypadku nanoszenia klasa Test z @Transactional oznacza, że ​​nie ma sterowania na wytrwałość danych, dlaczego ludzie nawet używać go w swoich badaniach ?

Aby zapobiec wypełnianiu bazy danych danymi testowymi.

  1. Nawet jeśli dane nie zostały zapisane w bazie danych, są utrwalone, prawda? Czy wywołanie reository.delete nie powinno usunąć tego elementu z kontekstu utrwalania z ?

Gdzie można sprawdzić, czy element został usunięty z kontekstu trwałości?

  1. Kod, który nie ma wpływu na db (usuwanie) znajduje się w warstwie Usługa. Z poziomu kontrolera wywoływane jest, że jestem testerem, a nie klasą testową. Spodziewałem się, że zadziała niezależnie od tego, czy klasa testowa ma adnotację @Transacational, czy też nie.

Każda metoda w teście jest pakowana w transakcję Spring, więc dane mogą nie zostać zatwierdzone do momentu zakończenia testu.

Sprawdź szczegółowych odpowiedzi:

+0

Wiem, że został wycofany, ale został wycofany po zakończeniu testu. Nie w środku. W przeciwnym razie dowolny test z adnotacją '@ Transactional' nie działałby wcale. –

+0

Dodałem trochę więcej szczegółów do pytania. –

+0

Może się zdarzyć, że test @Transactional nie będzie również zatwierdzał DB danych/trafień. – Justas

0

Z szerszej perspektywy myślę, że możesz używać niewłaściwego narzędzia do pracy.

Testy należy odizolować od siebie, aby kolejność przeprowadzonych testów nie miała wpływu na wyniki.

Uzyskuje się to w JUnit na przykład przez utworzenie nowej instancji klasy testowej dla każdej metody testowania, która ma zostać wykonana.

Dla testów integracyjnych, które testują logikę w ramach konkretnej transakcji, można to osiągnąć, uruchamiając transakcję na początku testu, a następnie wycofując ją po zakończeniu testu. Baza danych nie zawiera więc żadnych danych specyficznych dla testu i dlatego jest gotowa do użycia przez następny test.

Do testowania kontrolera odpoczynku może być wymagane inne podejście. Prawdopodobnie rozpoczynasz transakcję gdzieś w tym kontrolerze, a nie zanim faktyczny kod kontrolera odpoczynku zostanie wywołany, gdy będzie działał w twoim środowisku produkcyjnym. Możesz mieć przypadki, w których kontrolery powodują komunikację z innymi systemami niż db (jeśli są one dozwolone w testach kontrolera). Lub możesz mieć przypadki, w których wiele transakcji jest wykonywanych w ramach tego samego wywołania kontrolera odpływu lub transakcji, która korzysta z nie-domyślnej izolacji transakcji i tak dalej. Przypadki te nie będą działać z domyślnym zachowaniem przypadków testowych @Transactional.

Możesz więc ponownie rozważyć swoje podejście testowe i określić zakres testowy każdego zestawu testów. Następnie, w oparciu o te zakresy, można zdefiniować strategię izolowania testów w każdym z zakresów. Następnie dla każdego uruchomienia testu zastosuj odpowiednią strategię.

Powiązane problemy