使用模拟验证错误消息,其中结果可以根据 id 更改

Verify with mocks an error message where result can change based on id

所以我进行了一些单元测试,其中我模拟了一些对象,并使用它们来编写测试。

我正在使用 anyInt() 参数匹配器设置模拟对象。我想验证一个名为 displayErrorMessage(String errorMsg) 的方法。此方法接受一个字符串,并将其输出到 GUI 中。它接受的字符串在被发送到具有相关成员 ID 的显示方法之前被格式化。

我想再次使用字符串格式的参数匹配器将正确的错误消息传递给验证语句。

String.format("Member %d cannot borrow at this time.", anyInt());

我知道 anyInt() returns 零。假设我可以手动验证 displayErrorMessage() ,但这似乎不正确。

当前测试代码:

@Test
public void borrowingRestrictedWhenCardSwipedHasExceededFineLimit() throws Exception {
    when(memberDAO.getMemberByID(anyInt())).thenReturn(member);
    when(member.hasReachedFineLimit()).thenReturn(true);

    ctl.initialise();
    ctl.cardSwiped(anyInt());

    String errorMessage = "Member %d cannot borrow at this time.";
    errorMessage = String.format(errorMessage, anyInt());

    verify(ui).displayErrorMessage(errorMessage);
}

此验证适用于这种情况:

verify(ui).displayErrorMessage("Member 0 cannot borrow at this time.");

如果我处理不当,有什么更好的方法吗?

由于这个解释有点长,这里给出答案...

anyInt() 在需要匹配时用作参数。主要有两种情况:

when( myMock.someMethod( anyInt() ).thenReturn( x );

这使得 myMock return "x" 每当调用 someMethod 时 - 不管实际的 int 是什么。所以...

myMock.someMethod( 12 ) // will return x
myMock.someMethod( -180 ) // will return x
myMock.someMethod( 42 ) // will return x
// etc. ANY int parameter you give, will lead to x, since you told Mockito so in the when statement.

另一种使用方法是验证:

verify(myMock, times(1)).someMethod( anyInt() );

如果从未使用任何 int 调用 someMethod,这只会引发错误。如果它是用 12 或 -180 或 42 或 1 或 2 或 3 等调用的,这将没问题(只要它只被调用一次 - 不是每个 int 一次,而是总共调用一次)。

当然 anyInt() 有一个 int 值,因为它必须放在一个 int 的位置,但我会完全忽略那个值并且从不依赖它。

当最后调用真正的方法时,你不应该使用 anyInt() 而是一个真正的值,因为这会给你更多的控制权。在你的情况下,老实说,我根本不会使用 anyInt():

@Test
public void borrowingRestrictedWhenCardSwipedHasExceededFineLimit() throws Exception {
    when(memberDAO.getMemberByID(42)).thenReturn(member);
    when(member.hasReachedFineLimit()).thenReturn(true);

    ctl.initialise();
    ctl.cardSwiped(42);

    String errorMessage = "Member %d cannot borrow at this time.";
    errorMessage = String.format(errorMessage, 42);

    verify(ui).displayErrorMessage(errorMessage);
}

为什么?因为这样你可以确定,当调用 ctl.cardSwiped 时,它实际上会使用 memberDAO.getMemberByID 调用的参数。使用 anyInt() 你不能。例如,ctl.cardSwiped:

中可能存在错误
memberDao.getMemberById( parameter - 1);

when anyInt(),结果仍然是会员。

您知道您的测试用例将给出 42 作为参数,并且只有在给出 42 时,您才想要 return 成员。所以你测试给定的参数是否真的用于内部(memberDao)对象的调用。

所以,至少调用ctl。用实数而不是 anyInt,最好甚至替换所有 anyInt() 调用,因为它们实际上并没有在这里改进您的测试用例,而是实际上降低了测试质量。

使用anyInt并不意味着测试证明任何int都可以工作。使用什么整数是您的工作。例如,是否允许使用负整数?零?等等

编辑: 因为它已在另一个答案中提出......是的,当然另一种可能性是 "dumb down" 它没有的错误消息'不需要实际的 ID。问题是:错误信息重要吗? id重要吗?我猜是的,但这取决于用例。因此,如果确切的错误消息很重要,您将必须验证确切的错误消息 - 并在错误消息发生变化时调整您的测试。但这完全没问题,测试是为了确保特定的行为——如果确切的错误消息很重要,它是否是该特定行为的一部分并且必须进行测试。如果这种行为随后发生变化,单元测试的工作就是哭"Boo-Hoo, that method doesn't do what it's supposed to do anymore."。这就是单元测试的目的。 如果特定的错误消息不重要但它包含 id 很重要,那么您可以对此进行验证(使用 containsArgumentCaptor)。 如果重要的是调用错误方法,而不考虑实际消息,则验证 anyString() 并完成它。

Answer 接口可用于与模拟和调用其方法的参数进行交互。

doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) throws Throwable {
        String msg = (String) args[0];
        //Asserting the arguments with which mock method was invoked
        Assert.assertEquals("expected", msg);
        return null;
    }
}).when(ui).displayErrorMessage(any(String.classs));