Mockito

Mockito

本文整理自 B站 UP主 心蓝说Java 汪文君Mockito实战视频笔记

introduction

Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors. Read more about features & motivations.

significance of mockito

Automated tests are a safety net. They run and notify if the system is broken so that the offending code can be fixed very quickly. If a test suite runs for an hour, the purpose of quick feedback is compromised.

The following are example s of testing-unfriendly behaviors:

  • Accquiring a database connection and fetching/updating data
  • Connecting to the Internet and downloading files
  • Interacting with an SMTP server to send an e-mail
  • Looking up JNDI (Java Naming and Directory Interface])
  • Invoking a web service
  • Performing I/O operations, such as printing a report

Mockito plays a key role in mocking external dependencies.

1.**how to mock **

  • @RunWith(MockitoJUnitRunner.class)
@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();
        Mockito.when(request.getParameter("username")).thenReturn("alex");
        Mockito.when(request.getParameter("password")).thenReturn("123456");
        Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(account);
        assertThat(controller.login(request), equalTo("index.jsp"));
    }

    @Test
    public void testLoginFailure() {
        Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(null);
        assertThat(controller.login(request), equalTo("/login"));
    }

    @Test
    public void testLogin505() {
        Mockito.when(accountDao.findAccount(anyString(), anyString())).thenThrow(UnsupportedOperationException.class);
        assertThat(controller.login(request), equalTo("505"));
    }
}
  • MockitoAnnotations.initMocks(this) and @Mock

    public class AccountControllerTest {
        private AccountLoginController controller;
        private AccountDao accountDao;
    
        @Mock
        private HttpServletRequest request;
    
        @Before
        public void setUp(){
            MockitoAnnotations.initMocks(this);
            accountDao = Mockito.mock(AccountDao.class);
            controller = new AccountLoginController(accountDao);
        }
    
        @Test
        public void testLoginSuccess(){
            Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(new Account());
            assertThat(controller.login(request), equalTo("index.jsp"));
        }
    }
    
  • MockitoRule mockRule = MockitoJUnit.rule()

    public class AccountControllerMockByRuleTest {
        @Rule
        public MockitoRule mockitoRule = MockitoJUnit.rule();
    
        @Mock(answer = Answers.RETURNS_SMART_NULLS)
        private AccountDao accountDao;
    
        @Test
        public void testLogin(){
            Account account = accountDao.findAccount(anyString(), anyString());
            assertNotNull(account);
        }
    }
    
  • mock 方法内部类

    public class DeepMockOrderServiceTest {
        @Mock
        private Order order;
    
        @Mock(answer = Answers.RETURNS_DEEP_STUBS)
        private OrderService service;
    
        @Before
        public void setUp() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testOrderService(){
    //        Mockito.when(service.getOrder()).thenReturn(order);
    //        order.foo();
            service.getOrder().foo();
        }
    }
    

2.how to stubbing

  • stubbing void method
  • stubbing method with exception
  • stubbing consecutive calls(iterator-style-stubbing)
  • stubbing with callbacks
  • stubbing with real call
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

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

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class StubbingTest {
    private List<String> list;

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

    /**
     *  when ... Then ...
     */
    @Test
    public void howToUseStubbing(){
        when(list.get(0)).thenReturn("first");
        assertThat(list.get(0), equalTo("first"));
        when(list.get(anyInt())).thenThrow(new RuntimeException());
        try {
            list.get(0);
            fail();
        }catch (Exception e){
            assertThat(e, instanceOf(RuntimeException.class));
        }
    }

    /**
     *  测试无返回值的方法
     */
    @Test
    public void howToStubbingVoidMethod(){
        doNothing().when(list).clear();
        list.clear();
        // 验证执行次数
        verify(list, times(1)).clear();

        // 抛出异常
        doThrow(RuntimeException.class).when(list).clear();
        try {
            list.clear();
            fail();
        }catch (Exception e){
            assertThat(e, instanceOf(RuntimeException.class));
        }
    }

    /**
     *  测试有返回值的方法
     */
    @Test
    public void stubbingDoReturn(){
        when(list.get(0)).thenReturn("first");
        doReturn("second").when(list).get(1);
        assertThat(list.get(0), equalTo("first"));
        assertThat(list.get(1), equalTo("second"));
    }

    /**
     *  每次执行方法返回不同值
     */
    @Test
    public void iterateStubbing(){
        when(list.size()).thenReturn(1, 2, 3, 4);
//        when(list.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);

        assertThat(list.size(), equalTo(1));
        assertThat(list.size(), equalTo(2));
        assertThat(list.size(), equalTo(3));
        assertThat(list.size(), equalTo(4));
        assertThat(list.size(), equalTo(4));
    }

    /**
     *  返回自定义值
     */
    @Test
    public void stubbingAnswer(){
        when(list.get(anyInt())).thenAnswer(invocationOnMock -> {
            Integer index = invocationOnMock.getArgumentAt(0, Integer.class);
            return String.valueOf(index * 10);
        });
        assertThat(list.get(0), equalTo("0"));
        assertThat(list.get(777), equalTo("7770"));
    }

    /**
     *  mock对象真实调用方法
     */
    @Test
    public void stubbingWithRealMethod(){
        StubbingService service = mock(StubbingService.class);
        when(service.getStr()).thenReturn("alex");
        assertThat(service.getStr(), equalTo("alex"));

        when(service.getInt()).thenCallRealMethod();
        assertThat(service.getInt(), equalTo(10));
    }
    @After
    public void destroy(){
        reset(this.list);
    }
}

public class StubbingService {
    public int getInt(){
        return 10;
    }

    public String getStr(){
        throw new RuntimeException();
    }
}

3.mockito spying

Spying on real objects can be associated with “partial mocking” concept. Before the release 1.8, Mockito spies were not real partial mocks. The reason was we thought partial mock is a code smell. At some point we found legitimate use cases for partial mocks(3rd party interfaces, interim refactoring of legacy code).

import java.util.ArrayList;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class SpyingTest {

    @Test
    public void testSpy(){
        ArrayList<String> realList = new ArrayList<>();
        ArrayList<String> spyList = spy(realList);

        spyList.add("Mockito");
        spyList.add("PowerMock");

        assertThat(spyList.get(0), equalTo("Mockito"));
        assertThat(spyList.get(1), equalTo("PowerMock"));
        assertThat(spyList.isEmpty(), equalTo(false));

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

        assertThat(spyList.get(0), equalTo("Mockito"));
        assertThat(spyList.get(1), equalTo("PowerMock"));
        assertThat(spyList.isEmpty(), equalTo(true));
        assertThat(spyList.size(), equalTo(0));
    }
}
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

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

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;

public class SpytingAnnotationTest {
    @Spy
    private List<String> list = new ArrayList<>();

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

    @Test
    public void testSpying(){
        list.add("Mockito");
        list.add("PowerMock");

        assertThat(list.get(0), equalTo("Mockito"));
        assertThat(list.get(1), equalTo("PowerMock"));
        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("PowerMock"));
        assertThat(list.isEmpty(), equalTo(true));
        assertThat(list.size(), equalTo(0));
    }
}

4.Mockito Argument Matchers

  • The argument matcher plays a key role in mocking. Mock objects return expected values, but when need to return different values for different arguemnts, the argument matcher comes into play.

  • Mockito returns expected values when a method is stubbed. If the method takes arguments, the argument must match during the execution.

  • Mockito verifies argument values in natural Java style by using an objects’s equals() method.

  • Sometimes, we use argument matchers when extra flexibility is required. Mockito provides built-in matchers, such as anything(), anyDouble(), anyString(), anyList(), and anyCollection().参考链接

  • isA(Class clazz), any(Class clazz), eq(primitive value)

    import org.junit.Test;
    import org.mockito.Mockito;
    
    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.Mockito.*;
    
    public class ArgumentsMatcher {
        @Test
        public void basicTest(){
            List<Integer> list = mock(ArrayList.class);
            when(list.get(0)).thenReturn(100);
            assertThat(list.get(0), equalTo(100));
            assertThat(list.get(1), nullValue());
        }
    
        /**
         * diff isA(), any()
         */
        @Test
        public void testComplex(){
            Foo foo = mock(Foo.class);
            when(foo.function(Mockito.isA(Child1.class))).thenReturn(100);
            int result = foo.function(new Child1());
            assertThat(result, equalTo(100));
    
            result = foo.function(new Child2());
            assertThat(result, equalTo(0));
    
            reset(foo);
    
            when(foo.function(Mockito.any(Child1.class))).thenReturn(200);
            result = foo.function(new Child2());
            assertThat(result, equalTo(200));
        }
    
        static class Foo{
            int function(Parent parent){
                return parent.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();
            }
        }
    }
    

5.Working With WildCard Matchers

  • A test invokes a method on a code under test. When the invoke method creates a new object and passess that to a mock object, the test method doesn’t have the reference of that new object. So that test cannot stub the mock method with a specific value, as the value is not available to the test mehod. In this context, we use the wildcard matchers.

  • anyXXX()

  • any()

  • isA()

  • eq()

  • etc.

    import org.junit.After;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.mockito.runners.MockitoJUnitRunner;
    
    import java.io.Serializable;
    import java.util.Collections;
    
    import static org.hamcrest.CoreMatchers.equalTo;
    import static org.junit.Assert.assertThat;
    import static org.mockito.Matchers.*;
    import static org.mockito.Mockito.*;
    
    @RunWith(MockitoJUnitRunner.class)
    public class WildCardArgumentsMatcherTest {
        @Mock
        private SimpleService simpleService;
    
        @Test
        public void testWildcardMethod1(){
            Mockito.when(simpleService.method1(anyInt(), anyString(), anyCollection(), isA(Serializable.class))).thenReturn(100);
            int result = simpleService.method1(1, "he", Collections.emptySet(), "Mockito");
            assertThat(result, equalTo(100));
    
            result = simpleService.method1(1, "he", Collections.emptyList(), "MockitoForJava");
            assertThat(result, equalTo(100));
        }
    
        @Test
        public  void wildcardMethodWithSpec(){
            // 大范围的在最前面stub
            when(simpleService.method1(anyInt(), anyString(), anyCollection(), isA(Serializable.class))).thenReturn(-1);
            // 所有参数全为wildcard,不能部分参数为wildcard
            // 特定值用eq(), 否则报错
            when(simpleService.method1(anyInt(), eq("alex"), anyCollection(), isA(Serializable.class))).thenReturn(100);
            when(simpleService.method1(anyInt(), eq("he"), anyCollection(), isA(Serializable.class))).thenReturn(200);
            
            // specific value
            int result = simpleService.method1(1, "alex", Collections.emptyList(), "Mockito");
            assertThat(result, equalTo(100));
    
            result = simpleService.method1(1, "he", Collections.emptyList(), "Mockito");
            assertThat(result, equalTo(200));
    
          /*  // 没匹配上,返回类型默认值
            result = simpleService.method1(1, "sdfgbgfd", Collections.emptyList(), "Mockito");
            assertThat(result, equalTo(0));*/
    
            // 返回wildcard值
            result = simpleService.method1(1, "sdfgbgfd", Collections.emptyList(), "Mockito");
            assertThat(result, equalTo(-1));
        }
    
        @Test
        public void wildcardMethod2(){
            doNothing().when(simpleService).method2(anyInt(), anyString(), anyCollection(), isA(Serializable.class));
            simpleService.method2(1, "alex", Collections.emptyList(), "Mockito");
            verify(simpleService, times(1)).method2(1, "alex", Collections.emptyList(), "Mockito");
        }
    
        @After
        public void destroy(){
            // 还原stub
            reset(simpleService);
        }
    }
    
    

6.Hamcrest matcher

Hamcrest provides a utility matcher class, org.hamcrest.CoreMatchers. A few methods for CoreMatchers include allOf, anyOf, both, either, descibedAs, everyItem, is, isA, anything, hasItem, hasItems, equalTo, any, instanceOf, not, nullValue, notNullValue, sameInstance, end the Instance. It also includes a few string methods such as startsWith, endWith, and containsString. All these methods return a matcher.

import org.junit.Test;

import java.util.stream.Stream;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

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

    @Test
    public void matcher02(){
        double price = 23.45;
        assertThat(price, either(equalTo(23.45)).or(equalTo(23.54)));
        assertThat(price, both(equalTo(23.45)).and(not(equalTo(23.53))));

        assertThat(price, anyOf(is(23.45), is(33.44), is(333.666), not(55.66)));
        assertThat(price, allOf(is(23.45), not(is(34.77)), not(88.78)));

        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 matcher03(){
        double price = 23.45;
        assertThat("the double value assertion failed", price, either(equalTo(23.54)).or(equalTo(24.35)));
    }
}

7.Extend Matcher

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

public class CompareThan<T extends Number> extends BaseMatcher<T> {
    private final T value;
    private final boolean greater;

    public CompareThan(T value, boolean greater) {
        this.value = value;
        this.greater = greater;
    }

    @Override
    public boolean matches(Object actual) {
        Class<?> clazz = actual.getClass();
        if (clazz == Integer.class) {
            return greater ? (Integer) actual > (Integer) value : (Integer) actual < (Integer) value;
        }else if (clazz == Long.class){
            return greater ? (Long) actual > (Long) value : (Long) actual < (Long) value;
        }else if (clazz == Short.class){
            return greater ? (Short) actual > (Short) value : (Short) actual < (Short) value;
        }else if (clazz == Byte.class){
            return greater ? (Byte) actual > (Byte) value : (Byte) actual < (Byte) value;
        }else if (clazz == Float.class){
            return greater ? (Float) actual > (Float) value : (Float) actual < (Float) value;
        }else if (clazz == Double.class){
            return greater ? (Double) actual > (Double) value : (Double) actual < (Double) value;
        }else{
            throw new AssertionError("unsupported number type");
        }
    }

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

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

    @Override
    public void describeTo(Description description) {
        description.appendText("compare two numbers failed");
    }
}
import org.junit.Test;

import static com.heju.mockito.CompareThan.gt;
import static com.heju.mockito.CompareThan.lt;
import static org.hamcrest.CoreMatchers.both;
import static org.junit.Assert.assertThat;


public class MyAssertThatTest {
    @Test
    public void ltAndGt(){
        assertThat(12, gt(10));
        assertThat(12, both(gt(10)).and(lt(13)));
    }
}

refactor code

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


public class CompareNumberImpl<T extends Number> implements CompareNumber<T>{
    private final boolean greater;

    public CompareNumberImpl(boolean greater) {
        this.greater = greater;
    }

    @Override
    public boolean compare(T actual, T expected) {
        Class<?> clazz = expected.getClass();
        if (clazz == Integer.class) {
            return greater ? (Integer) expected > (Integer) actual : (Integer) expected < (Integer) actual;
        }else if (clazz == Long.class){
            return greater ? (Long) expected > (Long) actual : (Long) expected < (Long) actual;
        }else if (clazz == Short.class){
            return greater ? (Short) expected > (Short) actual : (Short) expected < (Short) actual;
        }else if (clazz == Byte.class){
            return greater ? (Byte) expected > (Byte) actual : (Byte) expected < (Byte) actual;
        }else if (clazz == Float.class){
            return greater ? (Float) expected > (Float) actual : (Float) expected < (Float) actual;
        }else if (clazz == Double.class){
            return greater ? (Double) expected > (Double) actual : (Double) expected < (Double) actual;
        }else{
            throw new AssertionError("unsupported number type");
        }
    }
}
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;

public class CompareNumberMatcher<T extends Number> extends BaseMatcher<T> {
    private final T value;

    private final CompareNumber<T> COMPARE;

    public CompareNumberMatcher(T value, boolean greater) {
        this.value = value;
        this.COMPARE = new CompareNumberImpl<>(greater);
    }

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

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

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

    @Override
    public void describeTo(Description description) {
        description.appendText("compare two numbers failed");
    }
}
import org.junit.Test;

import static org.hamcrest.CoreMatchers.both;
import static org.junit.Assert.assertThat;


public class MyAssertThatTest {

    @Test
    public void compareThan(){
        assertThat(12, CompareNumberMatcher.gt(10));
        assertThat(12, both(CompareNumberMatcher.gt(10)).and(CompareNumberMatcher.lt(13)));
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值