我应该如何处理对基础数据结构中的排序敏感的UnnecessaryStubbingException?

时间:2018-08-16 14:41:49

标签: java exception java-8 mockito

我有一个测试,期望发现用户被暂停时会引发异常。

  @Test(expected = SuspendedException.class)
  public void testGetUserKeychain_WhenOneUserSuspended_ShouldThrowSuspended() throws Throwable {
    when(userKeychain.getUserStatus()).thenReturn(UserState.OK);
    when(otherUserKeychain.getUserStatus()).thenReturn(UserState.SUSPENDED);
    when(keyLookup.lookupKeychainsByUserId(any()))
        .thenReturn(CompletableFuture.completedFuture(ImmutableMap.copyOf(multiUserKeychains)));
    try {
      padlockUtil.getKeychains(
          Sets.newSet("userid", "otheruserid")).toCompletableFuture().get();
    } catch (ExecutionException e) {
      throw e.getCause();
    }
  }

但是我得到的例外是:

org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected in test class: PadlockUtilTest
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.xyz.server.padlock.PadlockUtilTest.testGetUserKeychain_WhenOneUserSuspended_ShouldThrowSuspended(PadlockUtilTest.java:119)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.

我认为这是因为在PadlockUtil::getKeychains中,在OK用户之前 遇到了暂停用户,因此Mockito抱怨​​不需要OK用户。 >

因为我要交换谁被暂停而不是...

when(userKeychain.getUserStatus()).thenReturn(UserState.SUSPENDED);
when(otherUserKeychain.getUserStatus()).thenReturn(UserState.OK);

...然后Mockito很高兴。与仅切换"userid""otheruserid"不同; Mockito仍然不满意,大概是因为那不是以后确定订单的地方。

在我设置的这个特定示例中,可能没有必要对第一个用户进行打桩。但这将来可能会误导人。我希望存根存在,这不是因为“宽容”,IMO。我也可以暂停第一个用户而不是第二个用户,但是它并没有明确解决这种细微问题,并且稍后可能会再次出现,使开发人员感到困惑。

执行此操作的正确方法是什么,以使基本的操作顺序(我们在这里处理“集”和“地图”,别无其他)不是测试中的因素?

2 个答案:

答案 0 :(得分:3)

Lenient mocks是您想要的,如果您不能只使用真实的UserKeychain。

Mockito.lenient().when(userKeychain.getUserStatus()).thenReturn(UserState.OK);
Mockito.lenient().when(otherUserKeychain.getUserStatus()).thenReturn(UserState.SUSPENDED);

Mockito旨在替代您无法在测试中使用真实系统的系统,尤其是在可预测地调用服务而不是从数据对象获取属性的系统中(或其他幂等动作)。因为您的系统不会按确定的顺序调用这些方法,并且由于这些调用并不昂贵且没有副作用,所以我建议仅使用“宽松”选项。


想象一下这种情况,您正在测试删除用户1001

when(userRpc.deleteUser(1001)).thenReturn(RPC_SUCCESS);
when(userRpc.deleteUser(1002)).thenReturn(RPC_SUCCESS);  // unnecessary

如果您删除了错误的用户,则测试可能会通过:过度插入掩盖了问题。比较一下:

when(userRpc.fetchExpensiveUserDetails(1001)).thenReturn(user1001);
when(userRpc.fetchExpensiveUserDetails(1002)).thenReturn(user1002);  // unnecessary

根据您要测试的内容,这可能很危险,但可能还不错。模拟慢速的移动网络或使用昂贵的数据,也许这与您获取太多数据完全不合规格。但是,在其他情况下,这是可以接受的。最后,比较这种情况:

when(calculationResult.getRealComponent()).thenReturn(-1d);
when(calculationResult.getComplexComponent()).thenReturn(5);
when(calculationResult.getShortString()).thenReturn("-1 + 5i");

calculationResult看起来非常像数据对象,它可能不是测试中要调用哪个方法或是否要调用所有方法的关键部分。在这种情况下,Mockito的严格存根妨碍了您而不是帮助了您,并且可能是您想让其中一些存根宽大的情况。您可能还选择使整个模拟变宽,如果要创建像stubCalculationResult(-1, 5)这样的测试帮助程序方法来为您准备一个完整的对象,这特别有意义。

比这更好的唯一选择是使用真实对象。在我的示例中,如果CalculationResult是一个现有的定义明确的对象,则使用真实对象可能比模拟您在编写测试时相信的行为正确的总体风险要低。同样,对于您的情况,如果可以访问填充UserStatus等的UserKeychain构造函数,那么在测试中使用它可能会更安全。

尽管乍看之下可能会显得有些生疏,但是却将单元测试变成了集成测试,但我想澄清一下,我建议仅针对 data对象,它们没有依赖关系,理想情况下是不具有副作用的不可变对象。如果您使用依赖项注入,那么这些类型就是您将调用new而不是从图形中获取的单一实现数据持有者的类型。这也是分隔数据对象以使其不变且易于构造,并且转移服务以使用这些对象而不是为数据对象提供方法的好理由(偏爱loginService.login(user)而不是{{1} }。

答案 1 :(得分:1)

我在抛出不必要的存根异常的类中使用了@MockitoSettings(strictness = Strictness.LENIENT) 注释。已解决“请删除不必要的存根或使用‘宽松’严格性”错误。