Mockito单元测试

为什么要用单元测试

假如有一个非常大的项目,这个项目代码逻辑很复杂的情况,如何让它在特定的条件和入参执行到一块具体的代码逻辑,直接运行项目进行测试的代价是很高的,单元测试就可以快速的定位到具体逻辑去测试这块逻辑是否有问题。

什么是单元测试

单元测试就是对一个类的行为进行测试,通俗来说,每个方法都有入参和返回值,输入不同的参数会得到不同的返回值,或者异常;那么单元测试就是根据不同的use case来模拟不同场景执行,验证我们的代码在不同的执行路径下,是否会返回预期的结果。
单元测试是相互独立的,不应该依赖于外部资源(DB、第三方接口、网络链接),在任何时间任何环境下执行结果都是相同的。

Mockito

在日常工作中,我们基本都会基于spring这种容器框架来做开发,并且大部分情况下,项目都会依赖外部系统,比如DB、分布式缓存、第三方接口等。那么我们在针对某个类进行单元测试的时候,就会遇到各种困难:操作DB造成脏数据、第三方接口测试环境不可达等等。这时候,我们就需要一种方式屏蔽这些依赖组件对测试的影响,mockito就是这样一种mock工具,帮我们解决上述这些问题,当然类似的工具还有easymock、powermock、基于groovy的Spock等。

pom依赖

<dependency>
  <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.11</version>
   <scope>test</scope>
 </dependency>
 <dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-all</artifactId>
   <version>1.10.19</version>
   <scope>test</scope>
 </dependency>

案例:模拟登录

// An highlighted block
public class Account {
}
public class AccountDao {
    public Account findAccount(String username,String password){
        //假设DB不可用
        throw new UnsupportedOperationException();
    }
}
public class AccountLoginController {

    private AccountDao accountDao;

    public AccountLoginController(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    public String login(HttpServletRequest request){
        final String username = request.getParameter("username");
        final String password = request.getParameter("password");
        try {
            Account account = accountDao.findAccount(username, password);
            if(account == null){
                return "/login";
            }else {
                return "/index";
            }
        }catch (Exception e){
            return "505";
        }
    }
}

@RunWith(MockitoJUnitRunner.class)
public class AccountLoginControllerTest {

    private AccountDao accountDao;

    private HttpServletRequest request;

    private AccountLoginController controller;

    @Before
    public void setUP(){
        this.accountDao = Mockito.mock(AccountDao.class);
        this.request = Mockito.mock(HttpServletRequest.class);
        this.controller = new AccountLoginController(accountDao);
    }

    @Test
    public void testLoginSuccess(){
        Account account = new Account();
        when(request.getParameter("username")).thenReturn("Allen");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(),anyString())).thenReturn(account);
        assertThat(controller.login(request),equalTo("/index"));
    }

    @Test
    public void testLoginFailuer(){
        when(request.getParameter("username")).thenReturn("Allen");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(),anyString())).thenReturn(null);
        assertThat(controller.login(request),equalTo("/login"));
    }

    @Test
    public void testLogin505(){
        when(request.getParameter("username")).thenReturn("Allen");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(),anyString())).thenThrow(UnsupportedOperationException.class);
        assertThat(controller.login(request),equalTo("505"));
    }
}

Mock的三种方式

1. @RunWith(MockitoJUnitRunner.class)
// An highlighted block
@RunWith(MockitoJUnitRunner.class)
public class MockByRunner {

    @Test
    public void  testMock(){
        AccountDao accountDao = mock(AccountDao.class);
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }
}

输出 null   查看mock()源码可以看到里边ReturnsEmptyValues最终返回的null
如果想自定义其他的answer的方式


@RunWith(MockitoJUnitRunner.class)
public class MockByRunner {
    @Test
    public void  testMock(){
        AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }
}
输出
SmartNull returned by this unstubbed method call on a mock:
accountDao.findAccount("x", "x");

2.MockitoAnnotations.initMocks(this) & @Mock

// An highlighted block
public class MockByAnnocation {
    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

//    @Mock
    @Mock(answer = Answers.RETURNS_SMART_NULLS)//自定义answer方式
    private AccountDao accountDao;

    @Test
    public void testMock(){
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }
}

3.MockitoRule mockitoRule = MockitoJUnit.rule()

// An highlighted block
public class MockByRule {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();
    @Mock
    private AccountDao accountDao;

    @Test
    public void testMock(){
    	
        //AccountDao accountDao = mock(AccountDao.class);//使用@Mock注解或者mock()都可以
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }
}
输出 null

通常情况使用前两种比较多

DeepMock

public class Order {
    public void foo(){
    	//模拟异常
        throw new RuntimeException();
    }
}

public class OrderService {
    public Order get(){
    	//模拟异常
        throw new RuntimeException();
    }
}

public class DeepMockTest {

    @Mock
    private OrderService orderService;
    @Mock
    private Order order;
    
    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void testMock(){
        Order order = orderService.get();
        //orderService.get()返回的是null,在调用方法肯定异常
        order.foo();
    }
}
运行异常  java.lang.NullPointerException 

如何使返回的order不为null呢?就需要对orderService.get() 进行 stubbing操作
通俗点讲就是造数据,对入参和出参预期值的预设

// An highlighted block
public class DeepMockTest {

    @Mock
    private OrderService orderService;
    @Mock
    private Order order;

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

    @Test
    public void testMock(){
        when(orderService.get()).thenReturn(order);
        Order o1 = orderService.get();
        o1.foo();
    }
}


有没有一种方式 在Mock orderService的时候顺便把get出来的order也mock了


// Answers.RETURNS_DEEP_STUBS
public class DeepMockTest {
	
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)//deepmock 看情况使用,不一定是期望的返回值
    private OrderService orderService;

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void testMock(){
        orderService.get().foo();
    }
}

Mockito Stubbing的使用

Stubbing就是对入参和出参预期值的预设

public class StubbingService {
    public int getInt(){
        System.out.println("------getI()-----");
        return 10;
    }
    public String getSomething(){
        throw  new RuntimeException();
    }
}


@RunWith(MockitoJUnitRunner.class)
public class StubbingTest {

    private List<String> list;

    @Before
    public void init(){
        this.list =mock(ArrayList.class);
    }

    @Test
    public void howToUserStubbing(){
    	//list本身是空的  get(0)的时候拟定一个返回值"first"
        when(list.get(0)).thenReturn("first");
        //断言list.get(0)是否等于"first"
        assertThat(list.get(0),equalTo("first"));
		//拟定get()任意值的时候抛异常
        when(list.get(anyInt())).thenThrow(new RuntimeException());
        try{
            list.get(0);
            fail();
        }catch (Exception e){
        	//断言e 是否是RuntimeException
            assertThat(e,instanceOf(RuntimeException.class));
        }
    }

	//模拟void方法的stubbing
    @Test
    public void howToUserStubbingVoidMethod(){
    	//执行list.clear()后不做其他处理
        doNothing().when(list).clear();
        list.clear();
        //判断list.clear方法有没有执行?  执行了几次?
        verify(list,times(1)).clear();
		//执行clear()方法之后 抛异常
        doThrow(RuntimeException.class).when(list).clear();
        try{
            list.clear();
            fail();
        }catch (Exception e){
        	//断言是否抛了RuntimeException
            assertThat(e,instanceOf(RuntimeException.class));
        }
    }
    //模拟有返回值的 stubbing
    @Test
    public void stubbingDoReturn(){
    	//方式1
        when(list.get(0)).thenReturn("first");
        //方式2
        doReturn("second").when(list).get(1);
        assertThat(list.get(0),equalTo("first"));
        assertThat(list.get(1),equalTo("second"));
    }
    //模拟迭代的stubbing
    @Test
    public void interateStubbing(){
        when(list.size()).thenReturn(1,2,3,4);
        //断言第一次调用是否返回1
        assertThat(list.size(),equalTo(1));
        //断言第二次调用是否返回2
        assertThat(list.size(),equalTo(2));
        assertThat(list.size(),equalTo(3));
        assertThat(list.size(),equalTo(4));
    }
    //模拟每次循环后进行一些处理
    @Test
    public void stubbingWithAnswer(){
    	//普通@Override写法
//        when(list.get(anyInt())).thenAnswer(new Answer<String>() {
//            @Override
//            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
//                Integer index = invocationOnMock.getArgumentAt(0, Integer.class);
//                //todosomething
//                return String.valueOf(index * 10);
//            }
//        });
        //lombda的写法
        when(list.get(anyInt())).thenAnswer(invocationOnMock ->{
            Integer index = invocationOnMock.getArgumentAt(0, Integer.class);
            //todosomething
            return String.valueOf(index * 10);
        });
        //断言循环处理的结果是否是预期值
        assertThat(list.get(0),equalTo("0"));
        assertThat(list.get(10),equalTo("100"));
    }
	//模拟部分调用实际的逻辑
	@Test
    public void stubbingWithRealCall(){
    	//mock一个 StubbingService 
        StubbingService service = mock(StubbingService.class);
        //mock出的是CGLIB生成的代理,并不是实际的StubbingService 
        System.out.println(service.getClass());
        service.getSomething();//并不会报错
        when(service.getSomething()).thenReturn("Allen");
        assertThat(service.getSomething(),equalTo("Allen"));
		//thenCallRealMethod() 来调用实际的业务逻辑
        when(service.getInt()).thenCallRealMethod();
        assertThat(service.getInt(),equalTo(10));
    }

    @After
    public void destory(){
        //把stubbing动作都销毁掉
        reset(this.list);
    }
}

Stubbing的几种使用方式

when().thenReturn()
when().thenThrow()
doNothing().when(list).clear();
doThrow(RuntimeException.class).when(list).clear();
doReturn("second").when(list).get(1);
when(list.size()).thenReturn(1,2,3,4);//迭代
when(list.get(anyInt())).thenAnswer();//迭代
when(service.getInt()).thenCallRealMethod();
断言的方式
assertThat(xxx,equalTo("xxx"));
verify(list,times(1)).clear();

Mockito Spying的使用

其实就是先创建一个真实对象然后通过spy创建一个代理对象进行操作
对部分方法进行mock,进行stubbing的操作

@RunWith(MockitoJUnitRunner.class)
public class SpyingTest {

    @Test
    public void testSpy(){
        List<String> realList = new ArrayList<>();
        //mock一个list 就相当于是realList的代理
        List<String> list = spy(realList);
        list.add("Mockito");
        list.add("Power");
        System.out.println(list);
        System.out.println(realList);
        assertThat(list.get(0),equalTo("Mockito"));//成立 true
        assertThat(list.get(1),equalTo("Power"));//true
        assertThat(list.isEmpty(),equalTo(false));//true
		//对list方法的值进行stubbing预设
        when(list.isEmpty()).thenReturn(true);
        when(list.size()).thenReturn(0);
		
        assertThat(list.get(0),equalTo("Mockito"));//true
        assertThat(list.get(1),equalTo("Power"));//true
        //进行stubbing预设后会执行预设的方法
        assertThat(list.isEmpty(),equalTo(true));//true
        assertThat(list.size(),equalTo(0));//true
    }
}
打印:
[Mockito, Power]
[]

也可以通过注解的方式

public class SpyingAnnotationTest {


    @Spy
    private List<String> list = new ArrayList<>();

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

    @Test
    public void spyingByAnnotationTest(){
        list.add("Mockito");
        list.add("Power");

        assertThat(list.get(0),equalTo("Mockito"));
        assertThat(list.get(1),equalTo("Power"));
        assertThat(list.isEmpty(),equalTo(false));

        when(list.isEmpty()).thenReturn(true);
        when(list.size()).thenReturn(0);

        assertThat(list.get(0),equalTo("Mockito"));
        assertThat(list.get(1),equalTo("Power"));
        assertThat(list.isEmpty(),equalTo(true));
        assertThat(list.size(),equalTo(0));
    }
}

Mockito Argument Matcher

Mockito通过equals()方法,来对方法参数进行验证。但有时我们需要更加灵活的参数需求,比如,匹配任何的String类型的参数等等。参数匹配器就是一个能够满足这些需求的工具。
eq() :eq(0) 等价于 0
isA():isA(xxx.class)必须是xxx的实例
any():满足语法的任何类型都可以

package org.example.mockito.argument;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;

public class ArgumentsMatcherTest {


    @Test
    public void baseTest(){
        List<Integer> list = mock(ArrayList.class);
        // eq(0) 等价于  0
        when(list.get(eq(0))).thenReturn(100);
        assertThat(list.get(0),equalTo(100));
        assertThat(list.get(1),nullValue());
    }

    @Test
    public void testComplex(){

        Foo foo = mock(Foo.class);
        //isA(xxx.class)必须是xxx的实例
        when(foo.function(isA(Parent.class))).thenReturn(100);
        int result = foo.function(new Child1());
        assertThat(result,equalTo(100));

        result = foo.function(new Child2());
        assertThat(result,equalTo(100));

        //重置foo
        reset(foo);
        //any()  满足语法的任何类型都可以
        when(foo.function(any(Child1.class))).thenReturn(100);
        result = foo.function(new Child2());
        assertThat(result,equalTo(100));

    }

    static class Foo{
        int function(Parent p){
            return p.work();
        }
    }

    interface Parent{
        int work();
    }

    class Child1 implements Parent{
        @Override
        public int work() {
            throw new RuntimeException();
        }
    }
    class Child2 implements Parent{
        @Override
        public int work() {
            throw new RuntimeException();
        }
    }
}

Wildcard Argument Matcher

Mockito框架中的Matchers类内建了很多参数匹配器,而我们常用的Mockito对象便是继承自Matchers。这些内建的参数匹配器如,anyInt()匹配任何int类型参数,anyString()匹配任何字符串,anySet()匹配任何Set等。下面通过例子来说明如何使用内建的参数匹配器:
注意:同一个语句多个参数必须统一,如果使用wildcard那么所有的参数必须都使用wildcard,要么就都不使用,否则报错

package org.example.mockito.argument;


import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class WildcardArgumentMatcher {

    @Mock
    private SimpleService simpleService;

    @Test
    public void wildcardMethodTest(){
        when(simpleService.method1(anyInt(),anyString(),anyCollection(),isA(Serializable.class))).thenReturn(100);
        int result = simpleService.method1(1, "Allen", Collections.emptyList(), "Mockito");
        assertThat(result,equalTo(100));

        result = simpleService.method1(1, "Zhu", Collections.emptySet(), "MockitoForXXXX");
        assertThat(result,equalTo(100));
    }

    @Test
    public void testWilcardSpcMethod1(){
        //stubbing
//        when(simpleService.method1(anyInt(),anyString(),anyCollection(),isA(Serializable.class))).thenReturn(-1);
        when(simpleService.method1(anyInt(),eq("Allen"),anyCollection(),isA(Serializable.class))).thenReturn(100);
        when(simpleService.method1(anyInt(),eq("Zhu"),anyCollection(),isA(Serializable.class))).thenReturn(200);

        //正常匹配到
        int ressult = simpleService.method1(1,"Allen", Collections.emptyList(),"Mockito");
        assertThat(ressult,equalTo(100));
        ressult = simpleService.method1(1,"Zhu", Collections.emptyList(),"Mockito");
        assertThat(ressult,equalTo(200));
        //匹配不到
        ressult = simpleService.method1(1,"ZhuXXXXX", Collections.emptyList(),"Mockito");
        assertThat(ressult,equalTo(0));
//        assertThat(ressult,equalTo(-1));
    }

    @Test
    public void testWilcardSpcMethod2(){
        //stubbing
        List<Object> emptyList = Collections.emptyList();
        doNothing().when(simpleService).method2(anyInt(),anyString(),anyCollection(),isA(Serializable.class));
        simpleService.method2(1,"Allen",emptyList,"Mockito");
        //
        verify(simpleService,times(1)).method2(1,"Allen",emptyList,"Mockito");
        verify(simpleService,times(1)).method2(anyInt(),eq("Allen"),anyCollection(),isA(Serializable.class));
    }

    @After
    public void destroy(){
        reset(simpleService);
    }

}

Hamcrest Matcher一些简单使用

assertThat()的使用

package org.example.mockito.matcher;

import org.junit.Test;
import java.util.stream.Stream;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;

public class AssertMatcherTest {

    @Test
    public void test(){
        int i = 10;
        assertThat(i,equalTo(10));
        assertThat(i,not(equalTo(20)));
        assertThat(i,is(10));
        assertThat(i,is(not(20)));
    }

    @Test
    public void test2(){
        double price = 10.23;
        // or
        assertThat(price,either(equalTo(10.23)).or(equalTo(20.23)));
        // and
        assertThat(price,both(equalTo(10.23)).and(not(equalTo(20.23))));
        // 任意一个满足
        assertThat(price,anyOf(is(10.23),is(10),is(10),not(23.23)));
        // 所有的都要满足
        assertThat(price,allOf(is(10.23),not(is(20.23)),not(is(30.23))));
        // 任意一个满足
        assertThat(Stream.of(1,2,3).anyMatch( i -> i > 2),equalTo(true));
        // 所有的都要满足
        assertThat(Stream.of(1,2,3).allMatch( i -> i > 0),equalTo(true));

    }

    @Test
    public void test3(){
        double price = 10.23;
        // 断言失败输出语句
        assertThat("the double value assertThat failed.",price,both(equalTo(10.23)).and(equalTo(20.23)));

    }
}

自定义Matcher

package org.example.mockito.matcher;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;

public class GreaterThan<T extends Number> extends BaseMatcher {

    private final T value;

    private final Compare<T> COMPARE;

    public GreaterThan(T value, boolean greate) {
        this.COMPARE = new DefaultNumberCompare<>(greate);
        this.value = value;
    }


    @Override
    public boolean matches(Object actual) {
        return this.COMPARE.compare(value,(T)actual);
    }

    public static <T extends Number> GreaterThan<T> gt(T value){
        return new GreaterThan<>(value,true);
    }

    public static <T extends Number> GreaterThan<T> lt(T value){
        return new GreaterThan<>(value,false);
    }

    private interface Compare<T extends Number>{
        boolean compare(T expected,T actual);
    }

    private static class DefaultNumberCompare<T extends Number> implements Compare<T>{
        private final Boolean greate;

        public DefaultNumberCompare(Boolean greate) {
            this.greate = greate;
        }

        @Override
        public boolean compare(T expected, T actual) {
            Class<?> calzz = actual.getClass();
            if(calzz == Integer.class){
                return greate ? (Integer) actual > (Integer) expected : (Integer) actual < (Integer) expected;
            }else if (calzz == Byte.class){
                return greate ? (Byte) actual > (Byte) expected:(Byte) actual < (Byte) expected;
            }else if (calzz == Short.class){
                return greate ? (Short) actual > (Short) expected:(Short) actual < (Short) expected;
            }else if (calzz == Long.class){
                return greate ? (Long) actual > (Long) expected :(Long) actual < (Long) expected;
            }else if (calzz == Double.class){
                return greate ? (Double) actual > (Double) expected:(Double) actual < (Double) expected;
            }else if (calzz == Float.class){
                return greate ? (Float) actual > (Float) expected:(Float) actual < (Float) expected;
            }else {
                throw new AssertionError("The number type "+calzz+" not support.");
            }
        }
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("compare two num failed.");
    }
}


package org.example.mockito.matcher;

import org.junit.Test;
import static org.hamcrest.MatcherAssert.*;
import static org.example.mockito.matcher.GreaterThan.gt;

public class GreateTest {

    @Test
    public void test(){
        assertThat(10,gt(5));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值