`

Mockito 初探

阅读更多

 一. 简介

1.背景    

    Mockito是一个流行的Mocking(模拟测试)框架,通过使用Mocking框架,可以尽可能使unit test独立的。unit test保持独立的好处不在这里讨论。

    官方文档: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html 

 

2.Stub对象和Mock对象的区别

    Stub对象用来提供测试时所需要的测试数据,可以对各种交互设置相应的回应。例如我们可以设置方法调用的返回值等等。Mockito中when(…).thenReturn(…)。这样的语法便是设置方法调用的返回值。另外也可以设置方法在何时调用会抛异常等。
    Mock对象用来验证测试中所依赖对象间的交互是否能够达到预期。Mockito中用verify(…).methodXxx(…) 语法来验证 methodXxx 方法是否按照预期进行了调用。

 

3.依赖的配置

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

 

二.基础

1.Mocktio包的引入

(1) 为了方便的使用Mockito下的静态方法,在Eclipse 点击 preferences > Java > Editor > Content assist > Favorites > New Type。加入org.mockito.Mockito;

(2) preferences > Java > Code Style > Organize Import, 设置Number of static imports needed of * ,这里设置为1。

 

2.被测试目标类

为了方便说明,以下面这个类作为测试的目标

 

 

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public User getUserByLoginName(String loginName) {
        try {
            return userDao.findUserByLoginName(loginName);
        } catch (Exception e) {
            throw new RuntimeException("查找用户失败");
        }
    }

    @Override
    public String save(User user) {
        User oldUser = userDao.findUserByLoginName(user.getLoginName());
        if (oldUser != null) {
            throw new RuntimeException("已存在相同登录名");
        }
        try {
            userDao.save(user);
            return user.getId();
        } catch (Exception e) {
            throw new RuntimeException("新增用户失败", e);
        }
    }
}
 

 

3.创建Mock对象

(1)普通方法

普通方法中,被测试类可以写接口,因为可以手动new出实现类

 

public class UserServiceTest {
    private UserService userService;
    private UserDao mockUserDao;
    @Before
    public void setUp() {
        mockUserDao = mock(UserDao.class);
        userService = new UserServiceImpl();
        userService.setUserDao(mockUserDao);
    }
(2)通过注解

 

被@InjectMocks注解标注的属性,可以自动注入标记@Mock、@Spy等注解的属性值

@InjectMocks标注的属性不能使用接口,因为@InjectMocks不能传入参数指明实现类

 

public class UserServiceTest {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao mockUserDao;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
 

 

4.简单例子

 

    @Test
    public void getUserByLoginName() {
        User rtnUser = new User();
        rtnUser.setLoginName("admin");
        // Stud 设置方法调用的预期返回
        when(mockUserDao.findUserByLoginName(anyString())).thenReturn(rtnUser);
        User user = userService.getUserByLoginName("admin");
        // Mock 验证方法调用
        verify(mockUserDao, times(1)).findUserByLoginName(anyString());
        // assert 返回值是否和预期一样
        assertThat(user).isNotNull();
        assertThat(user.getLoginName()).isEqualTo("admin");
    }
 

 

(1) 设置方法调用的预期返回

    通过when(mock.someMethod()).thenReturn(value),来设定mock对象某个方法调用时的返回值。这里我们设定调用传入任意字符串调用findUserByLoginName方法,返回rtnUser。

(2)验证方法调用

    mock对象一旦创建,就会自动记录自己的交互行为。通过verify(mock).someMethod()方法,来验证方法是否被调用。这里我们验证了mockUserDao的findUserByLoginName方法被调用,并且通过times(1),验证是否被调用一次。注意,如果times不传入,则默认是1。

(3) assert返回值

最后通过assert断言返回值的正确性。这里使用的assertJ,具体内容可以参看 http://sgq0085.iteye.com/blog/2030609

 

三. mockito的使用

1.Mock 对象的期望行为及返回值设定

注意,Mockito的Stubbing有两种语法,并支持迭代。

 

(1)Stubbing对方法设定返回值

    when(mock.someMethod()).thenReturn(value1).thenReturn(value2);
    when(mock.someMethod()).thenReturn(value1, value2);
 

上面两种方式等同于

    when(mock.someMethod()).thenReturn(value1);
    when(mock.someMethod()).thenReturn(value2);
 

另一种风格doReturn,主要用于spy对象的情况下。

    doReturn(value1).doReturn(value2).when(mock).someMethod();
 

对 void 方法进行方法预期设定

    doNothing().when(mock).someMethod();
 

 

(2)对方法设定返回异常

    when(mock.someMethod()).thenThrow(new RuntimeException());
 

对 void 方法进行方法预期设定

    doThrow(new RuntimeException()).when(mock).someMethod();

(3)Argument Matcher(参数匹配器)

    Mockito提供了参数匹配器,用于灵活的匹配参数。

    比如 any(User.class),匹配任意User对象;anyString()匹配任意字符串;anyInt()匹配任意int型。

 

2.Mock对象的行为验证

看一下下面的例子

 

    @Test
    public void verifyTestTest() {
        List<String> mock = mock(List.class);
        List<String> mock2 = mock(List.class);
        when(mock.get(0)).thenReturn("Hello");
        when(mock.get(1)).thenReturn("World");
        mock.get(0);
        mock.get(1);
        // 验证指定方法被调用一次
        verify(mock).get(0);
        // 验证指定方法没有被调用
        verify(mock, never()).get(3);
        // 验证get方法在100毫秒内被调用两次
        verify(mock, timeout(100).times(2)).get(anyInt());

        // 通过验证方法的执行顺序
        InOrder inOrder = inOrder(mock, mock2);
        inOrder.verify(mock).get(0);
        inOrder.verify(mock).get(1);
        inOrder.verify(mock2, never()).get(1);

        // 查询多余的方法调用 mock所有调用的方法都已经被验证
        verifyNoMoreInteractions(mock);
        // 查询没有交互的mock对象
        verifyZeroInteractions(mock2);

        // 创建ArgumentCaptor(参数捕获器)用于捕获方法参数进行验证
        ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
        // 该方法被调用多次 只能捕捉到最后一次参数
        verify(mock, times(2)).get(argument.capture());
        assertThat(argument.getValue()).isEqualTo(1);
    }
 

 

(1)验证方法的调用次数

    通过 verify(mock,times(?)).someMethod()验证方法的调用次数,是常用的验证方式,包括可以验证指定明确的方法被调用次数、某方法未被调用。或一个方法总体被调用次数。

 

Mockito除了提供times(N)方法供我们调用外,还提供了很多可选的方法:
    never() 没有被调用,相当于times(0)
    atLeast(N) 至少被调用N次
    atLeastOnce() 相当于atLeast(1)
    atMost(N) 最多被调用N次

 

(2) 超时验证

    通过timeout,并制定毫秒数验证超时。注意,如果被调用多次,times还是需要的。

 

(3) 方法调用顺序

    通过InOrder对象,验证方法的执行顺序,如上例子中,如果mock的get(0)和get(1)方法反过来则测试不通过。这里mock2其实没有被调用过。所以不需要些。

 

(4) verifyNoMoreInteractions

    查询是否存在被调用,但未被验证的方法,如果存在则抛出异常。这里因为验证了get(anyInt()),相当于所有的get方法被验证,所以通过。

 

(5) verifyZeroInteractions

    查询对象是否未产生交互,如果传入的mock对象的方法被调用过,则抛出异常。这里mock2的方法没有被调用过,所有通过。

 

(6) 利用ArgumentCaptor(参数捕获器)捕获方法

    参数进行验证通过 ArgumentCaptor 对象的forClass(Class<T> clazz)方法来构建ArgumentCaptor对象。然后便可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。

 

当某个对象进行了多次调用后,比如mock对象。这时调用argument.getValue()获取到的是最后一次调用的参数。如果要获取所有的参数值可以调用argument.getAllValues(),它将返回参数值的List。

 

3.Spy-对象的监视

    Mock 对象只能调用stubbed 方法,调用不了它真实的方法。但Mockito 可以监视一个真实的对象,这时对它进行方法调用时它将调用真实的方法,同时也可以stubbing 这个对象的方法让它返回我们的期望值。另外不论是否是真实的方法调用都可以进行verify验证。和创建mock对象一样,对于final类、匿名类和Java的基本类型是无法进行spy的。

 

监视对象

    监视一个对象需要调用spy(T object)方法,如下面的代码中spy变量就在监视LinkedList实例。

 

List spy = spy(new LinkedList());

 

被监视对象的Stubbing

 

    stubbing 被监视对象的方法时要慎用when(Object)。比如下面的代码:

    List spy = spy(new LinkedList());
    // IndexOutOfBoundsException (the list is yet empty)
    when(spy.get(0)).thenReturn("foo");
    // You have to use doReturn() for stubbing
    doReturn("foo").when(spy).get(0);

这时,when方法参数中spy.get(0),调用的是真实list对象的get(0),这会产生IndexOutOfBoundsException异常,所以这时需要用到doReturn方法来设置返回值。

 

下面是一个官方给出的例子,仅供参考:

    @Test
    public void spyTest() {
        List list = new LinkedList();
        List spy = spy(list);
        // optionally, you can stub out some methods:
        when(spy.size()).thenReturn(100);
        // using the spy calls real methods
        spy.add("one");
        spy.add("two");
        // prints "one" - the first element of a list
        System.out.println(spy.get(0));
        // size() method was stubbed - 100 is printed
        System.out.println(spy.size());
        // optionally, you can verify
        verify(spy).add("one");
        verify(spy).add("two");
    }

 

4.总结

    最后,根据最开始的userService给出两个基于mockito测试方法来体现上述提到过的知识点。仅用来展示用法。

    @Test
    public void save() {
        User user = new User();
        user.setLoginName("admin");
        // 第一次调用findUserByLoginName返回user 第二次调用返回null
        when(mockUserDao.findUserByLoginName(anyString())).thenReturn(user).thenReturn(null);
        try {
            // 测试如果重名会抛出异常
            userService.save(user);
            // 如果没有抛出异常测试不通过
            failBecauseExceptionWasNotThrown(RuntimeException.class);
        } catch (ServiceException se) {
        }
        verify(mockUserDao).findUserByLoginName("admin");

        // userService.save(user);
        user.setPassword("123456");
        String userId = userService.save(user);
        // 断言返回结果
        assertThat(userId).isNotEmpty().hasSize(32);

        verify(mockUserDao, times(2)).findUserByLoginName(anyString());
        verify(mockUserDao).save(any(User.class));
    }

    @Test
    public void save2() {
        User user = new User();
        user.setLoginName("admin");
        user.setPassword("123456");
        userService.save(user);

        // 通过ArgumentCaptor(参数捕获器) 对传入参数进行验证
        ArgumentCaptor<User> argument = ArgumentCaptor.forClass(User.class);
        verify(mockUserDao).save(argument.capture());
        assertThat("admin").isEqualTo(argument.getValue().getLoginName());

        // stub 调用save方法时抛出异常
        doThrow(new ServiceException("测试抛出异常")).when(mockUserDao).save(any(User.class));
        try {
            userService.save(user);
            failBecauseExceptionWasNotThrown(RuntimeException.class);
        } catch (ServiceException se) {
        }
    }

 

 

永久链接: http://sgq0085.iteye.com/blog/2031319

分享到:
评论
2 楼 zgw06629 2016-08-02  
Thanks! 简洁明了。非常有帮助。
1 楼 sp42 2015-01-05  
非常不错!

相关推荐

    pring初探共18页.pdf.zip

    9. **测试支持**:Spring提供的单元测试和集成测试工具,如Spring Test和Spring Boot Test,以及Mockito等库的使用。 10. **Spring Batch**:处理批量操作的模块,适用于大数据量的读写,如日志处理、数据迁移等...

    junit4教程(《Junit4初探》)

    JUnit4的设计使其易于与其他库和框架集成,例如Mockito用于模拟对象,PowerMock用于测试静态方法和构造函数,或是TestNG提供更高级的测试功能。 ## 五、持续集成与持续测试 在持续集成环境中,JUnit4可以与Jenkins...

    Spring Boot 初探 | 第一篇:第一个Spring Boot程序(示例程序)

    **Spring Boot 初探:构建你的第一个应用** Spring Boot 是由 Pivotal 团队提供的全新框架,旨在简化 Spring 应用程序的初始搭建以及开发过程。它集成了大量常用的第三方库配置,如 JDBC、MongoDB、JPA、RabbitMQ、...

    JUnit4初体验

    本篇文章将带你初探JUnit4的魅力,了解其核心概念和使用方法。 首先,JUnit4引入了注解(Annotation)的概念,这使得测试类和方法的声明变得更加简洁明了。例如,`@Test`注解用于标记测试方法,表示该方法将会被...

    入门java web项目 高校马拉松

    【Java Web项目初探:高校马拉松】 在信息技术领域,Java Web技术是开发互联网应用程序的主流之一,尤其在教育环境中,如“高校马拉松”这样的项目,它通常被用来培养学生的编程能力和团队协作精神。Java Web项目...

    li-wms.rar

    《构建基于SpringBoot的Li-WMS系统初探》 在当今的IT行业中,SpringBoot以其简洁、高效的特点,成为开发企业级应用的首选框架。本文将通过解析名为"li-wms.rar"的压缩包文件,深入讲解如何利用Java语言和SpringBoot...

    springbillt.zip

    《Spring Boot初探:搭建与应用》 Spring Boot是由Pivotal团队提供的全新框架,它为简化Spring应用程序的初始搭建以及开发过程提供了便利。在Java领域,Spring Boot以其开箱即用、快速开发的特点,备受开发者青睐。...

    Android开发技巧总汇(2)

    - **架构实现**:通常,自动化测试会结合Mockito、Robolectric等库进行单元测试和集成测试。 - **模拟键盘鼠标事件**:通过Socket+Instrumentation或adb shell命令,可以模拟用户输入,例如点击、滑动等。 5. **...

    Android-SMoney:第一次练习Android记账本,第一次提交到GitHub上

    【Android-SMoney:初探Android应用开发与Git版本控制】 Android-SMoney是一个初学者的实践项目,用于学习和掌握Android应用程序开发,特别是针对记账应用的实现。这个项目表明了开发者正在逐步熟悉Java编程语言,并...

    Java-Handelsportal-:Java Handelsportal,首次GitHub测试

    【Java-Handelsportal: 初探Java电子商务平台】 在当今数字化时代,电子商务已经成为商业活动的重要组成部分。Java-Handelsportal项目就是一个典型的Java语言实现的电子商务平台,它为开发者提供了一个学习和实践...

Global site tag (gtag.js) - Google Analytics