And*_*ong 5 java exception mockito java-8
我有一个测试,期望在发现用户被挂起时抛出异常。
@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();
}
}
Run Code Online (Sandbox Code Playgroud)
但我得到的例外是:
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.
Run Code Online (Sandbox Code Playgroud)
我相信这是因为在 中PadlockUtil::getKeychains
,挂起的用户是在OK 用户之前遇到的,所以 Mockito 抱怨 OK 用户不需要被存根。
因为如果我交换谁被暂停而不是......
when(userKeychain.getUserStatus()).thenReturn(UserState.SUSPENDED);
when(otherUserKeychain.getUserStatus()).thenReturn(UserState.OK);
Run Code Online (Sandbox Code Playgroud)
......然后Mockito很高兴。与只是切换"userid"
和相反"otheruserid"
; 那里的 Mockito 仍然不高兴,大概是因为那不是以后确定顺序的地方。
在我设置的这个特定示例中,可能没有必要存根第一个用户。但这在未来可能会产生误导。我希望存根存在,这不是因为“宽大处理”,IMO。我也可以暂停第一个用户而不是第二个用户,但它没有明确解决这个微妙之处,而且以后可能会再次出现,使开发人员感到困惑。
这样做的正确方法是什么,以便底层的操作顺序(我们在这里处理 Sets 和 Maps,没有别的)不是测试中的一个因素?
Jef*_*ica 12
宽松的嘲笑是你想要什么,如果你不能只用一个真正的UserKeychain。
Mockito.lenient().when(userKeychain.getUserStatus()).thenReturn(UserState.OK);
Mockito.lenient().when(otherUserKeychain.getUserStatus()).thenReturn(UserState.SUSPENDED);
Run Code Online (Sandbox Code Playgroud)
Mockito 旨在替换您无法在测试中使用真实系统的系统,特别是在可预测地调用服务而不是从数据对象(或其他幂等操作)获取属性的系统中。因为您的系统不会以确定的顺序调用这些方法,并且因为调用不昂贵且没有副作用,所以我建议只使用“宽松”选项。
想象一下这种情况,您正在测试删除 user 1001
:
when(userRpc.deleteUser(1001)).thenReturn(RPC_SUCCESS);
when(userRpc.deleteUser(1002)).thenReturn(RPC_SUCCESS); // unnecessary
Run Code Online (Sandbox Code Playgroud)
如果您删除了错误的用户,测试可能会通过:过度存根掩盖了问题。与此比较:
when(userRpc.fetchExpensiveUserDetails(1001)).thenReturn(user1001);
when(userRpc.fetchExpensiveUserDetails(1002)).thenReturn(user1002); // unnecessary
Run Code Online (Sandbox Code Playgroud)
根据您正在测试的内容,这可能很危险,但也可能没有那么糟糕。模拟缓慢的移动网络或使用昂贵的数据,也许您获取太多数据完全违反规范。但是,在其他情况下,它可能是可以接受的。最后比较一下这个案例:
when(calculationResult.getRealComponent()).thenReturn(-1d);
when(calculationResult.getComplexComponent()).thenReturn(5);
when(calculationResult.getShortString()).thenReturn("-1 + 5i");
Run Code Online (Sandbox Code Playgroud)
calculationResult
看起来非常像一个数据对象,它可能不是测试的关键部分,调用哪些方法或是否调用所有方法。在这种情况下,Mockito 的严格存根阻碍了您而不是帮助您,并且可能是您想让其中一些存根变得宽容的情况。您还可以选择使整个模拟变得宽松,如果您要创建一个像stubCalculationResult(-1, 5)
这样为您准备整个对象的测试辅助方法,这尤其有意义。
唯一比这更好的选择是使用真实对象。在我的示例中,如果 CalculationResult 是一个现有的明确定义的对象,则使用真实对象的总体风险可能比模拟您在编写测试时认为正确的行为要低。与您的情况类似,如果您有权访问填充 UserStatus 等的 UserKeychain 构造函数,那么在测试中使用它可能更安全。
虽然这可能乍一看似乎是一个滑坡到把一个单元测试到集成测试,我想澄清的是,我只为推荐这个数据对象,其中有没有依赖性,并且最好是不可改变的对象没有副作用的方法。如果您使用依赖注入,这些是您将调用new
而不是从您的图中获取的单一实现数据持有者的类型。这也是一个很好的理由来分离您的数据对象,使它们不可变且易于构建,并转移您的服务以使用这些对象,而不是为数据对象提供方法(偏向loginService.login(user)
而不是user.login(loginService)
)。