Как имитировать метод сохранения репозитория JPA в модульных тестах

Например, у меня в UserService есть такой метод:

  @Override
  @Transactional
  public UserDto create(UserDto userDto) {

    User dbUser = userRepository.findOne(userDto.getId());

    if (dbUser != null) {
      throw new AuthException(AuthException.ErrorCode.DUPLICATE_USER_EXCEPTION);
    }

    User oneByLogin = userRepository.findOneByLogin(userDto.getLogin());
    if (oneByLogin != null) {
      throw new AuthExceptionAuthException.ErrorCode.DUPLICATE_LOGIN_EXCEPTION);
    }

    User newUser = new User();
    newUser.setGuid(UUID.randomUUID().toString());
    newUser.setInsertDate(new Date());
    newUser.setFirstName(userDto.getFirstName());
    newUser.setLastName(userDto.getLastName());
    newUser.setLogin(userDto.getLogin());
    newUser.setPassword(userDto.getPassword());
    newUser.setAuthToken(TokenGenerator.nextToken());
    newUser.setAuthTokenCreatedDate(new Date());

    User savedUser = userRepository.save(newUser);

    userDto.setAuthToken(savedUser.getAuthToken());
    log.info("User {0} created", savedUser.getLogin());
    return userDto;
  }

Как я могу создать модульный тест для этого метода? Я попробовал следующее:

  @Test
  public void createUser() {

    UserDto userDtoRequest = new UserDto();
    userDtoRequest.setLogin("Alex");
    userDtoRequest.setPassword("123");

    UserDto found = userService.create(userDtoRequest);
    assertThat(found.getAuthToken()).isNotEmpty();
}

У меня следующая логика:

  1. Старт теста
  2. User dbUser = userRepository.findOne(userDto.getId()); dbUser = NULL
  3. if (dbUser != null) и if (oneByLogin != null) пропускают
  4. создать нового пользователя и установить данные
  5. User savedUser = userRepository.save(newUser); savedUser = NULL

На этом этапе у меня проблема, потому что я не могу имитировать userRepository.save(newUser).

newUser create inside the method. and test fail.

saveUser.getAuthToken () - savedUser == NULL

Я могу переписать:

    userRepository.save(newUser);
    userDto.setAuthToken(newUser.getAuthToken());
    log.info("User {0} created", newUser.getLogin());
    return userDto;

но что, если я хочу использовать возвращенный объект savedUser?

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

grape_mao 09.07.2018 16:40

@ grape_mao и как мне это сделать? как мне вызвать метод userRepository.save (newUser), если объект newUser создает внутри служебного метода, а у меня его нет в моем тесте. когда (userService.save (???)). thenReturn (???);

ip696 09.07.2018 16:57

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

Alexander Polozov 09.07.2018 16:58

@ ip696 у вас есть два варианта. 1. проигнорируйте переданный аргумент, верните User с токеном. 2. Используйте что-то вроде doAnswer для имитации метода, чтобы вы могли уловить аргумент.

grape_mao 09.07.2018 17:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
23
5
56 288
4

Ответы 4

Вы можете сделать следующее:

@RunWith(MockitoJUnitRunner.class)
public class SimpleTest {

  @Mock
  private UserRepository mockedUserRepository;

  // .. your test setup

  @Test
  public void testYourMethod() {

     User userToReturnFromRepository = new User();
     userToReturnFromRepository.setAuthToken(YOUR_TOKEN);
     when(mockedUserRepository.save(any(User.class)).thenReturn(userToReturnFromRepository);

     UserDto found = userService.create(userDtoRequest);

     // ... your asserts

  }

}

при таком подходе вам просто нужно убедиться, что ваш mockedUserRepository внедрен в тестируемый класс (например, в конструктор).

Это рассматривает преобразование Entity в DTO внутри службы? repo 'save возвращает объект НЕ DTO, сервис должен его перевести

Kris Swat 16.10.2020 22:56

Вам нужно написать несколько тестовых случаев, чтобы протестировать разные сценарии.

Сценарий 1: когда findOne возвращает ненулевой объект:

@Test(expected=AuthException.class)
public void testCreateUserWhenAvailable()    {
     //Create one sample userDto object with test data
     when(mockedUserRepository.findOne(userDto.getId())).thenReturn(new User());
     userService.create(userDto);
}

Сценарий 2: когда findOneByLogin возвращает нулевой объект:

@Test(expected=AuthException.class)
public void testCreateUserWhenLoginAvailable()    {
     //Create one sample userDto object with test data
     when(mockedUserRepository.findOne(userDto.getId())).thenReturn(null);
     when(mockedUserRepository.findOneByLogin(userDto.getId())).thenReturn(new User());

     userService.create(userDto);
}

Сценарий 2: когда выполнено спасти:

@Контрольная работа

public void testCreateUserWhenSaved()    {
     //Create one sample userDto object with test data
     when(mockedUserRepository.findOne(userDto.getId())).thenReturn(null);
     when(mockedUserRepository.findOneByLogin(userDto.getId())).thenReturn(null);

     //Create sample User object and set all the properties
     User newUser=new User();
     when(mockedUserRepository.save(Mockito.any(User.class)).thenReturn(newUser);
     User returnedUser=userService.create(userDto);
     //You have two ways to test, you can either verify that save method was invoked by 
     //verify method
     verify(mockedUserRepository, times(1)).save(Mockito.any(User.class);
     //or by assertion statements, match the authToken in the returned object to be equal 
     //to the one set by you in the mocked object
     Assert.assertEquals(returnedUser.getAuthToken(),newUser.getAuthToken());
}

Наиболее полный ответ здесь.

IamDOM 15.03.2020 14:03

Вам нужно это сделать.

when(userRepository.save(Mockito.any(User.class)))
                .thenAnswer(i -> i.getArguments()[0]);

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

та же процедура, но с более читаемой функцией returnFirstArg () может быть найдена здесь stackoverflow.com/questions/2684630/…

schoener 17.06.2020 23:02

Всего два цента от меня о том, как создать метод save репозитория JPA с генерацией случайных идентификаторов для полей с помощью @GeneratedValue.

/**
 * Mocks {@link JpaRepository#save(Object)} method to return the
 * saved entity as it was passed as parameter and add generated ID to it.
 * If ID could not be generated, it will be ignored.
 * If parameter already has and ID, it will be overridden.
 */
private <T, V> void mockSave(JpaRepository<T, V> repository) {
    when(repository.save(any())).thenAnswer(i -> {
        Object argument = i.getArgument(0);
        Arrays.stream(argument.getClass().getDeclaredFields())
                .filter(f -> f.getAnnotation(GeneratedValue.class) != null)
                .forEach(f -> enrichGeneratedValueField(argument, f));
        return argument;
    });
}

Итак, здесь вы передаете желаемый репозиторий в качестве параметра, и методы вызывают enrichGeneratedValueField для всех полей, аннотированных аннотацией @GeneratedValue. Вот реализация этого метода:

private void enrichGeneratedValueField(Object argument, Field field) {
    try {
        if (field.getType().isAssignableFrom(Integer.class)) {
            FieldUtils.writeField(field, argument, Math.abs(random.nextInt()), true);
        } else {
            FieldUtils.writeField(field, argument, mock(field.getType()), true);
        }
    } catch (Exception ignored) {
    }
}

В этом примере я использовал только идентификаторы типа Integer, но заполните их, чтобы добавить желаемый тип идентификаторов.

Другие вопросы по теме