Java 如何模拟JPA存储库';单元测试中的save方法

Java 如何模拟JPA存储库';单元测试中的save方法,java,spring,unit-testing,mockito,Java,Spring,Unit Testing,Mockito,例如,我在UserService中有此方法: @Override @Transactional public UserDto create(UserDto userDto) { User dbUser = userRepository.findOne(userDto.getId()); if (dbUser != null) { throw new AuthException(AuthException.ErrorCode.DUPLICATE_USER

例如,我在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();
}
我有下一个逻辑:

  • 测试启动
  • User dbUser=userRepository.findOne(userDto.getId())dbUser=NULL
  • if(dbUser!=null
    )和
    if(oneByLogin!=null)
    跳过
  • 创建新用户并设置数据
  • User savedUser=userRepository.save(newUser)savedUser=NULL
  • 在这一步中,我遇到了一个问题,因为我无法模拟
    userRepository.save(newUser)

    savedUser.getAuthToken()-savedUser==NULL

    我可以重写:

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

    但是如果我想使用返回的对象savedUser怎么办?

    您可以执行以下操作:

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

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

    场景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);
    }
    
    @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:当findOneByLogin返回空对象时:

    @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);
    }
    
    @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完成时:

    @试验

    你需要这样做

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

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

    关于如何创建JPA存储库
    save
    方法,以及为具有
    @GeneratedValue
    的字段生成随机ID,我只需给您两分钱

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

    在本例中,我只使用了类型为
    Integer
    的ID,但可以自由填充以添加所需类型的ID。

    您正在测试您的服务,因此您应该像模拟存储库一样模拟其依赖关系。通过模拟方法
    save
    ,您可以指定它返回的对象。@grape\u mao以及如何执行?如果newUser对象创建了内部服务方法,而我的测试中没有该方法,那么如何调用userRepository.save(newUser)方法。当(userService.save(?)。然后返回(?);如果您不能为您的方法编写测试,这表明您需要将它分成几个部分,并分别进行测试。@ip696您有两个选择。1.忽略传入的参数,返回带有标记的
    用户
    。2.使用类似于
    doAnswer
    的方法来模拟该方法,以便捕获参数。此处可能重复最完整的答案。相同的过程,但可以在此处找到更可读的函数returnFirstArg(),这是服务内部考虑实体到DTO的转换吗?repo的“保存返回实体”不是DTO,服务应将其转换
    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) {
        }
    }