为什么要使用Mock?

     Mock 测试就是在测试过程中,创建一个假的对象,避免你为了测试一个方法,却要自行构建整个 Bean 的依赖链。

举个例子:

    类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很难测,因为你的返回结果会直接的 受外部服务影响,导致你的单元测试可能今天会过、但明天就过不了了。

    而当我们引入 Mock 测试时,就可以创建一个假的对象,替换掉真实的 Bean B 和 C,这样在调用B、C的方法时,实际上就会去调用这个假的 Mock 对象的方法,而我们就可以自己设定这个 Mock 对象的参数和期望结果,让我们可以专注在测试当前的类 A,而不会受到其他的外部服务影响,这样测试效率就能提高很多。

     比如你现在想要测试一个方法是否是正常的,但是这个方法中有很多调用数据库的代码,那么我们就可以在每个调用数据库的地方打桩,模拟一下访问完数据库之后的返回值,这样我们就可以在测试的时候避免访问数据库了,可以非常高效地完成我们的单元测试,已达到验证我们写的方法到底对不对的目的。

导入依赖

     如果你不想自己动手构建一套Mock解决方案,市面上也提供了很多现存的Mock方案。

     常用的有:EasyMock、Mockito 、WireMock、JMockit、Mock、Moco。

     在这里是使用Mockito做为本次演示使用。

<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.8.47</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

使用Mock模拟测试某个类中的某个方法是否可以成功执行

     如果我们想要测试某个类中的某个方法是否可以执行,我们不用直接调用这个类的这个方法,我们可以模拟一下这个类,让模拟的对象调用这个方法,然后再去检验一下这个模拟对象是否成功的调用到了这个方法,只要这个模拟对象成功调用到了这个方法,那么就说明,我们真实类中的这个方法是可以被成功执行的。这就是使用mockito来进行某个类的单元测试,如下图:

 /**
     * 通过mock模拟一个对象,然后检验这个对象的某个方法是否可以执行
     * */
    @Test
    public void test1(){
        //模拟创建一个List对象
        List mock = mock(List.class);
        //使用mock模拟出来的List对象,让这个对象添加一个元素1,(我们其实是为了验证List类中的add方法是否正常)
        mock.add(1);
        //验证模拟的对象List的add(1)方法是否执行,如果正常发生了,说明List类中的add(1)方法是正常的。这样我们的单元测试就算通过,
        //verify()的作用主要是检验我们模拟出来的这个对象中的方法是否成功执行,如果成功执行控制台什么信息都没有,如果没有成功
        //执行,控制台会报错误信息
        verify(mock).add(1);
    }

使用Mock模拟某个类的方法,自己给这个方法返回我们指定的值

     我们在测试一个控制器中的方法的时候,这个控制器中肯定是有一些方法是需要访问数据库的,但是我们自己在进行单元测试的时候,其实不必访问数据库,我们只需要知道访问数据库之后得到的这个值是什么,所以我们就可以使用Mock来模拟出访问数据库的方法返回的值,下面的这个例子就是我们自己给某个类中的方法直接指定一个返回值,如下图:

 /**
     * 模拟对象中的某个方法,给这个方法指定一个返回值,那么我们再执行这个模拟对象的方法的时候,返回的值就不再是真实
     * 对象返回的值,而应该是我们自己设置的返回值。
     * <p>
     * 比如我们这里有一个Iterator迭代器,原本调用迭代器对象的next()方法之后返回的值是集合中的下一个元素,我们这里来模拟
     * 这个方法的返回值,模拟的是第一次调用next()方法返回值是"hello",第二次调用next()方法返回值是"world",第三次以及往后调用
     * next()方法返回值是"abc"
     * <p>
     * 使用到了when(),thenReturn()方法,第一个thenReturn()代表第一次执行iterator.next()方法的返回值是"hello",
     * 第二个thenReturn()代表第二次执行iterator.next()方法的返回值是"world",
     * 第三个thenReturn()代表第三次即以后执行iterator.next()方法的返回值都是"abc"
     * <p>
     * 还是用到了assertEquals(猜想值,变量)断言方法
     */
    @Test
    public void test2() {
        //使用mock模拟出一个Iterator类
        Iterator iterator = mock(Iterator.class);

        //自己设置迭代器对象方法next()的返回值
        when(iterator.next()).thenReturn("hello").thenReturn("world").thenReturn("abc");

        //使用mock模拟的iterator对象,去看看iterator调用next()方法之后的返回值是否是我们想的那样
        String result = iterator.next() + " " + iterator.next() + " " + iterator.next() + " " + iterator.next();

        //使用断言验证猜想的结果是否正确
        assertEquals("hello world abc abc", result);
    }

使用Mock模拟某个方法调用后会抛出指定的异常

  /**
     * 使用Mock模拟对象,规定某个方法要抛出一个异常
     * */
    @Test(expected = IOException.class)
    public void test3() throws IOException {

        OutputStream outputStream = mock(OutputStream.class);

        //我们自己规定当执行OutputStream对象的close()方法的时候,会主动的抛出一个IOException异常
        doThrow(new IOException()).when(outputStream).close();

        outputStream.close();
    }

什么叫做打桩?以及什么情况下需要打桩什么情况下不需要打桩?

     打桩其实就是在真实代码的地方用一个模拟方法代替,然后真实方法执行到这个地方的时候,它的返回值是我们模拟的返回值。when().thenReturn(),用这句代码我们可以自己给某个方法设定返回值,这就叫做打桩。

在什么时候需要打桩呢?如果我们想要自测的方法有返回值,我们需要打桩。

在现实开发中遇到的问题

     我们大部分在开发的生活都会遇到这种情况,在一个开发中需要去调用外部的服务,但是由于外部需要远程调用或者还未开发完毕,这个时候为了能正常开发或者单元测试通过。我们需要进行mock这个服务。

例如以下案例:

一个学生的信息需要调用其他外部服务,但是由于外部服务还未开发好,因此先使用Mock进行测试开发。


//邮件服务
public interface MailService {
    public String getMail();
}
//地址服务
public interface AddressService {
    public String getAddress();
}

//获取信息汇总
public class StudentManager {
    @Resource
    private MailService mailService;
    @Resource
    private AddressService addressService;

    public String getStudentInfo(){
       return mailService.getMail()+addressService.getAddress();
    }
}

最后测试:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class StudentManagerTest{

    @InjectMocks
    private StudentManager studentManager;

    @Mock
    private AddressService addressService;

    @Mock
    private MailService mailService;

    @Test
    public void testGetStudentInfo() {
        Mockito.when(addressService.getAddress()).thenReturn("杭州市西湖区");
        Mockito.when(mailService.getMail()).thenReturn("123456@163.com");
        String studentInfo = studentManager.getStudentInfo();
        Assert.assertEquals("123456@163.com杭州市西湖区",studentInfo);
    }
}

 

使用方法可以参考mockito中文文档

hehonghui/mockito-doc-zh: Mockito框架中文文档 (github.com)。

欢迎关注微信公众号:Java的学习之路

里面资料非常全,从java初级到高级都有,视频,电子书,面试宝典,简历模板,经典案例,源码分析程序员故事以及解决bug方法。。。。应有尽有,可以推荐大家一起学习下!!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐