【问题标题】:How to mock JPA repository's save method in unit tests如何在单元测试中模拟 JPA 存储库的保存方法
【发布时间】:2018-12-17 06:29:27
【问题描述】:

比如我在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.

savedUser.getAuthToken() - savedUser == NULL

我可以重写:

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

但是如果我想使用返回的对象savedUser呢?

【问题讨论】:

  • 您正在测试您的服务,因此您应该像存储库一样模拟它的依赖项。通过 mock 方法save,可以指定它返回的对象。
  • @grape_mao 我该怎么做?如果 newUser 对象在服务方法中创建并且我在测试中没有它,我如何调用方法 userRepository.save(newUser)。 when(userService.save(???)).thenReturn(???);
  • 如果你的方法不能写测试,说明你需要把它分成几个部分,分别测试。
  • @ip696 你有两个选择。 1.忽略传入的参数,返回一个User,带有一个token。 2.使用doAnswer之类的东西来模拟方法,这样你就可以捕捉到参数。

标签: java spring unit-testing mockito


【解决方案1】:

你需要这样做。

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

现在您可以获得作为参数传递的用户。

【讨论】:

【解决方案2】:

您可以执行以下操作:

@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 被注入到您的测试类中(例如在构造函数中)。

【讨论】:

  • 这是在考虑服务内部的实体到 DTO 转换吗? repo 的保存返回实体 NOT DTO,服务应该翻译它
【解决方案3】:

你需要编写多个测试用例来测试不同的场景。

场景 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:save 完成后:

@测试

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

【讨论】:

  • 这里最完整的答案。
【解决方案4】:

关于如何创建 JPA 存储库 save 方法并为带有 @GeneratedValue 的字段生成随机 ID,我只需 2 美分。

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

因此,在这里您将所需的存储库作为参数传递,并且方法会为所有使用 @GeneratedValue 注释的字段调用 enrichGeneratedValueField。下面是这个方法的实现:

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 类型的 ID,但可以随意填写以添加所需的 ID 类型。

【讨论】:

    猜你喜欢
    • 2019-07-07
    • 2021-02-13
    • 1970-01-01
    • 2014-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-05
    • 2014-06-25
    相关资源
    最近更新 更多