Mockito中when()和doXxx()方法的区别

2023/05/12

1. 概述

Mockito是一个广泛使用的Java Mock框架。有了它,创建mock对象、配置mock行为、捕获方法参数以及验证与mock的交互都很简单。

在本文中,我们重点介绍如何指定mock行为。 我们有两种方法可以做到这一点:分别是when().thenDoSomething()和doSomething().when()语法。

2. when()

让我们考虑以下Employee接口:

public interface Employee {
    String greet();

    void work(DayOfWeek day);
}

在我们的测试中,我们使用这个接口的mock。假设我们想配置mock的greet()方法以返回字符串“Hello”。使用Mockito的when()方法的例子如下所示:

class WhenVsDoMethodsUnitTest {

    @Mock
    private Employee employee;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void givenNonVoidMethod_callingWhen_shouldConfigureBehavior() {
        // given
        when(employee.greet()).thenReturn("Hello");

        // when
        String greeting = employee.greet();

        // then
        assertThat(greeting, is("Hello"));
    }
}

employee对象是一个mock对象。当我们调用它的任何方法时,Mockito会注册该调用。 通过调用when()方法,Mockito知道这个调用不是业务逻辑的交互。 这是我们想要为mock对象分配一些行为的声明。之后,使用thenXxx()方法之一,我们指定预期的行为

到目前为止,我们的mock配置正常工作。同样,当我们使用Sunday参数调用work()方法时,我们希望将其配置为抛出异常:

class WhenVsDoMethodsUnitTest {

    @Test
    void givenVoidMethod_callingWhen_wontCompile() {
        // given
        when(employee.work(DayOfWeek.SUNDAY)).thenThrow(new IAmOnHolidayException());

        // when
        Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);

        // then
        assertThrows(IAmOnHolidayException.class, workCall);
    }
}

不幸的是,这段代码无法编译,因为在work(employee.work(…))调用中,work()方法的返回类型为void; 因此我们不能将它封装到另一个方法调用中。这是否意味着我们不能mock void方法?当然可以,接下来让我们看看如何实现这一点。

3. doXxx()

让我们看看如何使用doThrow()方法配置异常抛出:

class WhenVsDoMethodsUnitTest {

    @Test
    void givenVoidMethod_callingDoThrow_shouldConfigureBehavior() {
        // given
        doThrow(new IAmOnHolidayException()).when(employee).work(DayOfWeek.SUNDAY);

        // when
        Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);

        // then
        assertThrows(IAmOnHolidayException.class, workCall);
    }
}

这种语法与前一种略有不同:我们没有将返回值为void的方法调用包装在另一个方法调用中。因此,这段代码可以编译通过。

首先,我们在doThrow(…)中声明要抛出的异常类型。 接下来,我们调用了when()方法,并传递了mock对象。之后,我们指定了我们想要配置的mock交互行为

请注意,这与我们之前使用的when()方法不同。另外,我们在调用when()之后链接了mock交互。同时,我们用第一种语法在括号内定义了它。

为什么我们需要when().thenXxx()这种语法呢,当它不能完成像配置void调用这样的常见任务时,它与doXxx().when()语法相比具有多个优点。

首先,对于开发人员来说,编写和阅读诸如“当一些交互时,然后做一些事情”之类的语句比“做一些事,当一些交互时”更合乎逻辑

其次,我们可以通过链接将多个行为添加到同一个交互中。 那是因为when()返回OngoingStubbing<T>类的一个实例,而thenXxx()方法返回相同的类型。

另一方面,doXxx()方法返回一个Stubber实例,而Stubber.when(T mock)返回T,因此我们可以指定我们要配置什么样的方法调用。 但是T是我们应用程序的一部分,例如我们的代码片段中的Employee。而T不会返回Mockito类,因此我们无法通过链接添加多个行为。

4. BDDMockito

BDDMockito使用了与我们上面提到的另一种不同语法。这很简单:在我们的mock配置中,我们必须将关键字“when”替换为“given”,将关键字“do”替换为“will”。 除此之外,我们的代码保持不变:

class WhenVsDoMethodsUnitTest {

    @Test
    void givenNonVoidMethod_callingGiven_shouldConfigureBehavior() {
        // given
        given(employee.greet()).willReturn("Hello");

        // when
        String greeting = employee.greet();

        // then
        assertThat(greeting, is("Hello"));
    }

    @Test
    void givenVoidMethod_callingWillThrow_shouldConfigureBehavior() {
        // given
        willThrow(new IAmOnHolidayException()).given(employee).work(DayOfWeek.SUNDAY);

        // when
        Executable workCall = () -> employee.work(DayOfWeek.SUNDAY);

        // then
        assertThrows(IAmOnHolidayException.class, workCall);
    }
}

5. 总结

我们介绍了使用when().thenXxx()或doXxx().when()方式配置mock对象的优缺点。 此外,我们还了解了这些语法是如何使用的,以及为什么Mockito需要为我们提供这两种语法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章