在 Spring Boot 中使用模拟存储库测试无效服务方法?

Testing a void service method with mocked repository in Spring Boot?

我的 Spring 引导应用程序提供以下服务,该服务会增加 User 实体上的计数器并将其持久保存到数据库中。

@Service
public class MyService {
    
    @Autowired
    UserRepo userRepo;
    
    public void incrementLoginFailures(String email) throws UserNotFoundException {
        final User user = userRepo.findByEmail(email).orElseThrow(UserNotFoundException::new);
        user.incrementLoginFailures();
        userRepo.save(user);
    }
}

现在我想为此编写一个 JUnit 5 测试,最好是模拟存储库,但我想我在理解这里模拟的概念时遇到了一些困难。

如您所见,服务方法 incrementLoginFailures() 没有 return 任何东西,所以我不知道如何检查 User 实体上的登录计数器实际上是递增。我是否需要为这种实际启动数据库的检查编写集成测试?

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

  @InjectMocks
  private UserService userService;

  @Mock
  private UserRepo userRepo;

  @Test
  public void shouldIncrement() {
    userService.incrementLoginFailures("foo@bar.com");
    Assertions.assertEquals(1, xyz);  // from what source would xyz come from?
  }
}

在调用之前的测试方法中userService.incrementLoginFailures("foo@bar.com"); 您将需要模拟在 userRepo 上调用的方法的 return 值,如下所示:

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

  @InjectMocks
  private UserService userService;

  @Mock
  private UserRepo userRepo;

  @Test
  public void shouldIncrement() {
    User user = new User();//please initialize as per your requirement
    when(userRepo.findByEmail("foo@bar.com")).thenReturn(user);
    userService.incrementLoginFailures("foo@bar.com");//this method will make changes to the user object
    Assertions.assertEquals(1, user.getLoginFailures());
  }
}

我建议 return 从方法中获取用户对象以帮助测试。要设置模拟存储库行为,请使用 Mockito.when 和 return 新用户 POJO。然后在 returned 对象上断言登录失败计数为 1。

您应该只测试自己的代码,并在单元中测试该单元的代码。因此,例如,如果 Spring 数据存储库 save() 有效。您可以测试它,但您应该相信它是有效的。

如果做对了,您应该有一个单元测试断言 user.incrementLoginFailures() 可以正常工作。

// Unit test for the user
@Test
public void shouldIncrement() {
    User user = new User();
    // In here when you have just initialized user you might know that 
    // the initial count is zero but it is overall better to assert 
    // the logic for any value / case
    int originalLoginFailures = user.getLoginFailures();
    user.incrementLoginFailures();
    assertEquals(originalLoginFailures + 1, user.getLoginFailures());
 }

除了数据保存到数据库之外,这其实就是你需要知道的。

因此,您可能对 UserService(或者它是 MyService?)进行了单元测试,断言 userIncrementLoginFailures()userRepo.save() 是为电子邮件为 的用户调用的foo@bar.comuserService.incrementLoginFailures("foo@bar.com") 被调用时。

这将通过 verify 完成,例如:

@InjectMocks
private MyService userService;
@Mock
private UserRepo userRepo;
@Mock
private User user; 

@Test
public void shouldIncrementAndSave() throws UserNotFoundException {
    when(userRepo.findByEmail("foo@bar.com")).thenReturn(Optional.of(user));
    userService.incrementLoginFailures("foo@bar.com");
    verify(user).incrementLoginFailures();
    verify(userRepo).save(user);
}

你可以测试user.incrementLoginFailures()在这里也能工作,但是因为你在你的用户单元测试中测试了它不一定需要但是是冗余测试,测试不属于单元测试的逻辑,即UserService.