Mockito 测试基本用法

本文介绍了Mockito测试框架的基本概念和用法,包括mock测试、mock对象、Stub和Mock的区别、Mockito入门实例、参数匹配器、方法调用验证、按顺序验证、避免交互操作、快速创建mock对象、迭代式测试、测试桩和监控真实对象的使用。Mockito提供了一种简单且强大的方式来模拟和验证对象交互,以进行单元测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是mock测试,什么是mock对象?

先来看看下面这个示例:

这里写图片描述

从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。

一种替代方案就是使用mocks

这里写图片描述

从图中可以清晰的看出

mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。

知道什么是mock测试后,那么我们就来认识一下mock框架—Mockito

二、什么是Mockito

除了有一个好记的名字外,Mockito尝试用不一样的方法做mocking测试,是简单轻量级能够替代EasyMock的框架。使用简单,测试代码可读性高,丰富的文档包含在javadoc中,直接在IDE中可查看文档,实例,说明。更多信息:http://code.google.com/p/mockito/

三、Stub和Mock

相同点:Stub和Mock对象都是用来模拟外部依赖,使我们能控制。

不同点:
- 而stub完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。
- 而mock对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。
- 在mocking框架中mock对象可以同时作为stub和mock对象使用,两者并没有严格区别。

更多信息:http://martinfowler.com/articles/mocksArentStubs.html

四、mockito入门实例

  1. Maven依赖:
<dependency>
  <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
</dependency>
  1. 基本用法
    @Test
    @SuppressWarnings("unchecked")
    public void testSimple() throws Exception {
        //创建mock对象
        /*创建mock对象不能对final,Anonymous ,primitive类进行mock。*/
        List<String> list = mock(List.class);

        //设置方法的预期返回值
        when(list.get(0)).thenReturn("hello world!");
        //doReturn("hello wolrd!").when(list).get(0);

        //可以设定返回异常
        when(list.get(1)).thenThrow(new RuntimeException("exception!"));

        //没有返回值的void方法与其设定(支持迭代风格,第一次调用donothing,第二次dothrow抛出runtime异常)
        doNothing().doThrow(new RuntimeException("void exception")).when(list).clear();
        list.clear();
        list.clear();
        verify(list, times(2)).clear();

        String result = list.get(0);

        //验证方法调用
        verify(list).get(0);

        //junit测试
        Assert.assertEquals("hello world!", result);

    }

Ps:
创建mock对象不能对final,Anonymous ,primitive类进行mock。

五、参数匹配器(Argument Matcher)

Matchers类内加你有很多参数匹配器 anyInt、anyString、anyMap…..Mockito类继承于Matchers,Stubbing时使用内建参数匹配器,下例:

   /**
     * 参数匹配器
     * 如果使用参数匹配器,那么所有的参数都要使用参数匹配器,不管是stubbing还是verify的时候都一样。
     */
    @Test
    @SuppressWarnings("unchecked")
    public void argumentMatcherTest() throws Exception {

        List<String> list = mock(List.class);
        when(list.get(anyInt())).thenReturn("hello", "world", "hqq");
        String result = list.get(0) + list.get(1);
        verify(list, times(2)).get(anyInt());
        Assert.assertEquals("helloworld", result);

        Map<Integer, String> map = mock(Map.class);
        when(map.put(anyInt(), anyString())).thenReturn("hello");
        String str = map.put(1, "world");
        verify(map).put(eq(1), eq("world"));
        Assert.assertEquals("hello", str);
    }

需要注意的是:如果使用参数匹配器,那么所有的参数都要使用参数匹配器,不管是stubbing还是verify的时候都一样。

六、方法调用的验证(具体的调用次数、至少一次,一次也没有)

   /**
     * 方法调用的验证
     */
    @Test
    public void verifyInvocate() throws Exception {
        List<String> list = mock(List.class);

        list.add("one");
        list.add("twice");
        list.add("twice");
        list.add("three times");
        list.add("three times");
        list.add("three times");

        verify(list, times(1)).add("one");
        verify(list, times(2)).add("twice");
        verify(list, times(3)).add("three times");
        verify(list, times(0)).add("never added");

        verify(list, atLeastOnce()).add("one");
        verify(list, atLeast(2)).add("twice");
        verify(list, atMost(3)).add("three times");
    }

七、按顺序来验证

    /**
     * 按顺序来验证
     */
    @Test
    @SuppressWarnings("unchecked")
    public void orderTest() throws Exception {
        // A. 单个mock对象他的方法必须按照顺序来调用。
        List singleMock = mock(List.class);
        singleMock.add("first");
        singleMock.add("second");
        //为单个Mock创建一个InOrder的顺序验证
        InOrder inOrderSingle = inOrder(singleMock);
        //验证访问次序
        inOrderSingle.verify(singleMock).add("first");
        inOrderSingle.verify(singleMock).add("second");

        // B. 多个mock也必须按照顺序来使用
        List firstMock = mock(List.class);
        List secondMock = mock(List.class);

        firstMock.add("first add");
        secondMock.add("second add");

        InOrder inOrderMulti = inOrder(firstMock, secondMock);

        inOrderMulti.verify(firstMock).add("first add");
        inOrderMulti.verify(secondMock).add("second add");
    }

八、 确保交互操作没有执行在Mock对象上

//使用mock对象——仅和mockOne在进行交互
mockOne.add("one");

//普通验证
verify(mockOne).add("one");

//验证某个交互是否从未被执行
verify(mockOne, never()).add("two");

//验证其它Mock对象没有交互过
verifyZeroInteractions(mockTwo, mockThree);

九、查找多余的调用

//使用mock
mockedList.add("one");
mockedList.add("two");

verify(mockedList).add("one");

//下面的验证将会失败
verifyNoMoreInteractions(mockedList);

十、快速创建mock对象——@Mock注解

@RunWith(MockitoJUnitRunner.class)
public class AnnotationMockTest {

    @Mock
    List<String> list;

    @Mock
    private User user;

    @Test
    public void test() throws Exception {
        when(list.get(0)).thenReturn("hello,mockito");
        System.out.println(list.get(0));
    }

    @Test
    public void userTest() throws Exception {
        when(user.getName()).thenReturn("heqianqian");
        System.out.println(user.getName());
    }
}

十一、迭代式的测试

有时我们需要为同一个方法调用的返回值或者异常做测试桩。典型的运用就是使用Mock迭代器。Mockito的早期版本是没有这个特性的。比如,开发人员可能会使用Iterable或者是简单的集合来替代迭代器。这些方法为做测试桩提供了更自然的方式,在一些场景中为连续的调用做测试桩会很有用,比如:

@RunWith(MockitoJUnitRunner.class)
public class IteratorMockitoTest {

    @Mock
    List<String> list;

    @Test
    public void iteratorTest() throws Exception {
        when(list.get(0)).thenReturn("one","two","three");
        //返回one
        System.out.println(list.get(0));
        //返回two
        System.out.println(list.get(0));
        //之后都是three
        System.out.println(list.get(0));
        System.out.println(list.get(0));
    }
}

十二、为回调做测试桩

为泛型接口Answer打桩。

public class CallbackMockitoTest {

    @Test
    @SuppressWarnings("unchecked")
    public void callbackTest() throws Exception {
        List<String> list = mock(List.class);
        list.add("hello");
        when(list.get(0)).thenAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                //获得函数调用的参数
                Object[] args = invocationOnMock.getArguments();
                //获得Mock对象本身
                Object mock = invocationOnMock.getMock();
                return "called with arguments:" + args[0].toString();
            }
        });

        System.out.println(list.get(0));

    }
}

十三、监控真实对象

你可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时,真实的方法也会调用(除非这个函数被打桩)。实际中应该有节制的spy对象,比如在处理遗留代码时:

  List list = new LinkedList();
List spy = spy(list);

 //你可以随意的为某些函数打桩:
 when(spy.size()).thenReturn(100);

 //通过spy对象调用真实对象的方法:
 spy.add("one");
 spy.add("two");

 //打印list的第一个元素
 System.out.println(spy.get(0));

 //因为size()函数被打桩了,所以这里返回100
 System.out.println(spy.size());

 //验证交互
 verify(spy).add("one");
 verify(spy).add("two");

有时候,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。因此在使用spy的时候,请考虑doReturn|Answer|Throw() 这一系列的方法来打桩。例如:

 List list = new LinkedList();
 List spy = spy(list);

 //不可能:因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException,因为真实list对象是空的。
 when(spy.get(0)).thenReturn("foo");

 //你需要使用doReturn来进行打桩。
 doReturn("foo").when(spy).get(0);
  • Mockito并不会为真实对象代理函数调用,实际上它会复制真实对象。所以,如果你保留了真实对象并与其交互,不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被打桩的函数时,并不会调用真实对象的对应函数,因此你不会在真实对象上看到任何结果。

  • 因此结论就是:当你在监控一个真实对象时,你想为这个真实对象的函数做测试桩,那么就是在自找麻烦。或者你根本不应该验证这些函数。


<think>好的,用户想了解如何使用Mockito测试私有方法。首先,我需要回顾一下Mockito基本功能和限制。根据引用[3],Mockito主要用于模拟外部依赖,比如数据库调用或服务接口,但默认情况下不支持直接测试私有方法。不过用户提到PowerMock可能有解决方案,引用[4]中的示例展示了PowerMock如何测试静态方法,但用户需要的是私有方法测试。 接下来,我应该考虑Mockito本身是否提供了间接的方法来测试私有方法。根据引用[5],测试私有方法可能不是最佳实践,建议通过公共方法间接测试。但用户可能有特殊情况需要直接测试私有方法。这时候,可能需要结合反射来实现。 然后,我需要查阅相关资料,确认Mockito是否支持通过反射访问私有方法。根据经验,Java反射可以访问私有方法,结合Mockito的when/then语法可能可行。例如,使用PowerMockito的Whitebox类来设置私有字段或调用私有方法。 但用户提到之前尝试PowerMock无效,可能需要检查示例代码是否正确。引用[4]中的示例使用了@PrepareForTest和PowerMockRunner,这可能同样适用于私有方法测试。需要提供具体的步骤,比如添加依赖、使用PowerMockito.mockStatic或spy对象,然后使用Whitebox.invokeMethod来调用私有方法。 另外,引用[5]提到AssertJ和自定义断言可以增强测试可读性,这可能对用户的测试结构有帮助。同时,用户需要注意测试私有方法可能导致测试脆弱,因为实现细节变化会破坏测试,因此建议优先测试公共接口。 最后,总结步骤:使用PowerMockito结合反射,配置测试类,调用私有方法,并进行断言。同时提醒用户权衡测试私有方法的利弊,推荐最佳实践。</think>### 使用Mockito测试私有方法的解决方案 #### 核心思路 Mockito本身不直接支持测试私有方法,但可通过以下两种方式实现: 1. **反射机制**:通过Java反射强制调用私有方法并验证逻辑。 2. **PowerMock扩展**:结合PowerMock框架对私有方法进行模拟或调用[^4][^5]。 --- #### 方法1:反射机制(纯Mockito) 通过反射直接调用私有方法并断言结果,适用于验证私有方法逻辑的场景。 **示例代码**: ```java import org.junit.Test; import java.lang.reflect.Method; import static org.junit.Assert.assertEquals; public class PrivateMethodTest { @Test public void testPrivateMethod() throws Exception { MyClass myClass = new MyClass(); // 获取私有方法 Method method = MyClass.class.getDeclaredMethod("privateMethod", String.class); method.setAccessible(true); // 强制访问私有方法 // 调用方法并断言结果 String result = (String) method.invoke(myClass, "input"); assertEquals("expected_output", result); } } ``` --- #### 方法2:PowerMock + Mockito(模拟私有方法) 当需要模拟私有方法的行为时,可使用PowerMock的`Whitebox`工具类或`spy`机制。 **步骤**: 1. 添加依赖: ```xml <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> ``` 2. 测试代码示例: ```java import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import static org.junit.Assert.assertEquals; @RunWith(PowerMockRunner.class) public class PrivateMethodTest { @Test public void testPrivateMethodWithPowerMock() throws Exception { MyClass myClass = new MyClass(); // 直接调用私有方法 String result = Whitebox.invokeMethod(myClass, "privateMethod", "input"); assertEquals("expected_output", result); // 模拟私有方法返回值(需使用spy) MyClass spy = Mockito.spy(myClass); Whitebox.doReturn("mocked_value").when(spy, "privateMethod", "input"); String mockedResult = Whitebox.invokeMethod(spy, "privateMethod", "input"); assertEquals("mocked_value", mockedResult); } } ``` --- #### 注意事项 1. **可维护性**:直接测试私有方法可能导致测试代码与实现细节紧耦合,需谨慎使用[^5]。 2. **框架选择**: - 若仅需调用私有方法,反射机制足够。 - 若需模拟私有方法行为,需使用PowerMock。 3. **最佳实践**:优先通过公共方法间接测试私有逻辑,降低测试复杂度[^3][^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值