为什么要mock

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。

如下使用范畴

 

  • 真实对象具有不可确定的行为,产生不可预测的效果,(如:股票行情,天气预报)
  • 真实对象很难被创建的
  • 真实对象的某些行为很难被触发
  • 真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等

什么是类的部分mock(partial mock)?
A:部分mock是说一个类的方法有些是实际调用,有些是使用mockito的stubbing(桩实现)。

为什么需要部分mock?

A:当需要测试一个组合方法(一个方法需要其它多个方法协作)的时候,某个叶子方法(只供别人调用,自己不依赖其它反复)已经被测试过,我们其实不需要再次测试这个叶子方法,so,让叶子打桩实现返回结果,上层方法实际调用并测试。

mockito实现部分mock的两种方式:spy和callRealMethod()

 

Mockito

  • 官方文档
  • 大多 Java Mock 库如 EasyMock 或 JMock 都是 expect-run-verify (期望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法:在执行后的互动中提问
  • expect-run-verify 方式 也意味着,Mockito 无需准备昂贵的前期启动。他们的目标是透明的,让开发人员专注于测试选定的行为。

Mockito使用流程

mock--> stub ---> run --> verify(可选)

mock

创建mock对象有如下几种方法

1、mock

//mock creation
 List mockedList = mock(List.class);

 //using mock object
 mockedList.add("one");
 mockedList.clear();

Once created, a mock will remember all interactions. Then you can selectively verify whatever interactions you are interested in.

2、spy

   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");

 

注意:spy对象并不是真实的对象

public class TestSpy {

    @Test
    public void test(){
        List<String> list = new LinkedList<String>();
        List spy = spy(list);

        spy.add("one");

        doReturn(100).when(spy).size();

        // 返回one
        System.out.println(spy.get(0));
        //返回100
        System.out.println(spy.size());
        //返回0
        System.out.println(list.size());
        //抛异常java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        System.out.println(list.get(0));
    }
}

package spy;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.LinkedList;
import java.util.List;

import org.junit.Test;

public class SpyDemo {

    @Test
    public void spy_Simple_demo(){
        List<String> list = new LinkedList<String>();  
        List<String> spy = spy(list);  
        when(spy.size()).thenReturn(100);  
        
        spy.add("one");  
        spy.add("two");  
        
/*        spy的原理是,如果不打桩默认都会执行真实的方法,如果打桩则返回桩实现。
        可以看出spy.size()通过桩实现返回了值100,而spy.get(0)则返回了实际值*/
        assertEquals(spy.get(0), "one");  
        assertEquals(100, spy.size());  
    }
    
    @Test  
    public void spy_Procession_Demo() {  
        Jack spyJack = spy(new Jack());  
        //使用spy的桩实现实际还是会调用stub的方法,只是返回了stub的值
        when(spyJack.go()).thenReturn(false);  
        assertFalse(spyJack.go()); 
        
        //不会调用stub的方法
        doReturn(false).when(spyJack).go();
        assertFalse(spyJack.go()); 
    } 
    
}



class Jerry {
	public void goHome() {
		doSomeThingA();
		doSomeThingB();
	}
	// real invoke it.
	public void doSomeThingB() {
		System.out.println("good day");
	}
	// auto mock method by mockito
	public void doSomeThingA() {
		System.out.println("you should not see this message.");
		doSomeThingB();
	}
	public boolean go() {  
        System.out.println("I say go go go!!");  
        return true;  
    } 
 
}
//  当需要整体Mock,只有少部分方法执行真正部分时,选用这种方式
	@Test  
	public void callRealMethodTest() { 
	  
		Jerry jerry = Mockito.mock(Jerry.class);
		
		
	    Mockito.doCallRealMethod().when(jerry).goHome();  
	    Mockito.doCallRealMethod().when(jerry).doSomeThingB();  
	  
	    jerry.goHome();  
	  
	    Mockito.verify(jerry,Mockito.times(1)).doSomeThingA();  
	    Mockito.verify(jerry,Mockito.times(1)).doSomeThingB();  
	}  
	// 当需要整体执行真正部分,只有少部分方法执行mock,选用这种方式
	@Test  
	public void spyTest() {  
	    Jerry spyJack = Mockito.spy(new Jerry()); 
	    // 用thenReturn 会走go()方法体,然后将返回值Mock掉
	    Mockito.when(spyJack.go()).thenReturn(false);    
	    Assert.assertFalse(spyJack.go());  
	   // 用doReturn 不走go()方法体
	    Mockito.doReturn(false).when(spyJack).go();    
	    Assert.assertFalse(spyJack.go());  
	}

 

3、mock 和 spy 的区别

 

  • By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.(使用mock生成的对象,所有方法都是被mock的,除非某个方法被stub了,否则返回值都是默认值)
  • You can create spies of real objects. When you use the spy then the real methods are called (unless a method was stubbed).(使用spy生产的spy对象,所有方法都是调用的spy对象的真实方法,直到某个方法被stub后)
  • 总结:

        spy和callrealmethod都可以实现部分mock,唯一不同的是通过spy做的桩实现仍然会调用实际方法。

       批注:spy方法需要使用doReturn方法才不会调用实际方法。

Spy类就可以满足我们的要求。如果一个方法定制了返回值或者异常,那么就会按照定制的方式被调用执行;如果一个方法没被定制,那么调用的就是真实类的方法。

如果我们定制了一个方法A后,再下一个测试方法中又想调用真实方法,那么只需在方法A被调用前,调用Mockito.reset(spyObject);就行了。 

4、Shorthand for mocks creation - @Mock annotation

public class ArticleManagerTest {

       @Mock private ArticleCalculator calculator;
       @Mock private ArticleDatabase database;
       @Mock private UserProvider userProvider;

       private ArticleManager manager;
}

注意:为了是的上面的annotation生效,必须调用下面之一

  • MockitoAnnotations.initMocks(testClass);
MockitoAnnotations.initMocks(testClass)
  • use built-in runner: MockitoJUnitRunner

 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class ExampleTest {

     @Mockprivate List list;

     @Test
     public void shouldDoSomething() {
         list.add(100);
     }
   }
  • MockitoRule.
 public class ExampleTest {

     //Creating new rule with recommended Strictness setting
     @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

     @Mock
     private List list;

     @Test
     public void shouldDoSomething() {
         list.add(100);
     }
 }

stub

1、简单例子

 //You can mock concrete classes, not just interfaces
 LinkedList mockedList = mock(LinkedList.class);

 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

 //following prints "first"
 System.out.println(mockedList.get(0));

 //following throws runtime exception
 System.out.println(mockedList.get(1));

 //following prints "null" because get(999) was not stubbed
 System.out.println(mockedList.get(999));

 //Although it is possible to verify a stubbed invocation, usually it's just redundant
 //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
 //If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
 verify(mockedList).get(0);
  • Stubbing can be overridden: for example common stubbing can go to fixture setup but the test methods can override it. Please note that overridding stubbing is a potential code smell that points out too much stubbing
  • Once stubbed, the method will always return a stubbed value, regardless of how many times it is called.
  • Last stubbing is more important - when you stubbed the same method with the same arguments many times. Other words: the order of stubbing matters but it is only meaningful rarely, e.g. when stubbing exactly the same method calls or sometimes when argument matchers are used, etc.

2、Argument matchers

 //stubbing using built-in anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");

 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");

 //following prints "element"
 System.out.println(mockedList.get(999));

 //you can also verify using an argument matcher
 verify(mockedList).get(anyInt());

 //argument matchers can also be written as Java 8 Lambdas
 verify(mockedList).add(argThat(someString -> someString.length() > 5));
 

3、Stubbing void methods with exceptions

   doThrow(new RuntimeException()).when(mockedList).clear();

   //following throws RuntimeException:
   mockedList.clear();

4、Stubbing consecutive calls

when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

 //First call: throws runtime exception:
 mock.someMethod("some arg");

 //Second call: prints "foo"
 System.out.println(mock.someMethod("some arg"));

 //Any consecutive call: prints "foo" as well (last stubbing wins).
 System.out.println(mock.someMethod("some arg"));
 when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");

5、Stubbing with callbacks

when(mock.someMethod(anyString())).thenAnswer(new Answer() {
     Object answer(InvocationOnMock invocation) {
         Object[] args = invocation.getArguments();
         Object mock = invocation.getMock();
         return "called with arguments: " + args;
     }
 });

 //the following prints "called with arguments: foo"
 System.out.println(mock.someMethod("foo"));
 

verify

1、Verifying exact number of invocations / at least x / never

//using mock
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 //following two verifications work exactly the same - times(1) is used by default
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 //exact number of invocations verification
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost()
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

2、Verification in order

// A. Single mock whose methods must be invoked in a particular order
 List singleMock = mock(List.class);

 //using a single mock
 singleMock.add("was added first");
 singleMock.add("was added second");

 //create an inOrder verifier for a single mock
 InOrder inOrder = inOrder(singleMock);

 //following will make sure that add is first called with "was added first, then with "was added second"
 inOrder.verify(singleMock).add("was added first");
 inOrder.verify(singleMock).add("was added second");

 // B. Multiple mocks that must be used in a particular order
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);

 //using mocks
 firstMock.add("was called first");
 secondMock.add("was called second");

 //create inOrder object passing any mocks that need to be verified in order
 InOrder inOrder = inOrder(firstMock, secondMock);

 //following will make sure that firstMock was called before secondMock
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");

 // Oh, and A + B can be mixed together at will

3、Making sure interaction(s) never happened on mock


 //using mocks - only mockOne is interacted
 mockOne.add("one");

 //ordinary verification
 verify(mockOne).add("one");

 //verify that method was never called on a mock
 verify(mockOne, never()).add("two");

 //verify that other mocks were not interacted
 verifyZeroInteractions(mockTwo, mockThree);

4、Finding redundant invocations

 //using mocks
 mockedList.add("one");
 mockedList.add("two");

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

 //following verification will fail
 verifyNoMoreInteractions(mockedList);
 

partial mocking

spy对象是调用真实方法,mock的doCallRealMethod也是调用真实方法。当我们想partial mocking的时候,选择依据是根据要stub的方法的多少:

  • spy(全部真实除了stub)
  • mock doCallRealMethod(全部不真实 除了stub)

doReturn when && when thenReturn

  • when thenReturn会真实调用函数,再把结果改变
  • doReturn when不会真的去调用函数,直接把结果改变
  • 举例子
public class Jerry {

    public boolean go() {
        System.out.println("I say go go go!!");
        return true;
    }
}

public class SpyTest {

    @Test
    public void test(){
        Jerry spy = spy(new Jerry());

        when(spy.go()).thenReturn(false);

        //I say go go go!!
        //false
        System.out.println(spy.go());

        doReturn(false).when(spy).go();

        //false
        System.out.println(spy.go());

    }
}

PowerMock

Mockito 因为可以极大地简化单元测试的书写过程而被许多人应用在自己的工作中,但是Mock 工具不可以实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能。PowerMock 是在 EasyMock 以及 Mockito 基础上的扩展,通过定制类加载器等技术,PowerMock 实现了之前提到的所有模拟功能,使其成为大型系统上单元测试中的必备工具。

  • 类的静态方法

mockStatic

public class IdGenerator {

    public static long generateNewId() {
       return 100L;
    }
}

public class ClassUnderTest {

    public void methodToTest() {

        final long id = IdGenerator.generateNewId();
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(IdGenerator.class)
public class MyTestClass {

    @Test
    public  void demoStaticMethodMocking(){
        mockStatic(IdGenerator.class);
        when(IdGenerator.generateNewId()).thenReturn(2L);
        new ClassUnderTest().methodToTest();
//        verifyStatic();

        System.out.println(IdGenerator.generateNewId());



    }
}

  • 构造函数

whenNew(File.class).withArguments(direcPath).thenReturn(mockDirectory);

public class DirectoryStructure {

    public boolean create(String directoryPath) {
        File directory = new File(directoryPath);

        if (directory.exists()) {
            throw new IllegalArgumentException(
                "\"" + directoryPath + "\" already exists.");
        }

        return directory.mkdirs();
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(DirectoryStructure.class)
public class DirectoryStructureTest {

    @Test
    public void createDirectoryStructureWhenPathDoesntExist() throws Exception {

        final String direcPath = "mocked path";
        File mockDirectory = mock(File.class);

        when(mockDirectory.exists()).thenReturn(false);
        when(mockDirectory.mkdirs()).thenReturn(true);
        whenNew(File.class).withArguments(direcPath).thenReturn(mockDirectory);

        Assert.assertTrue(new DirectoryStructure().create(direcPath));

        verifyNew(File.class).withArguments(direcPath);
    }
}
  • private & final方法

when(underTest, nameOfMethodToMock, input).thenReturn(expected);

public class PrivatePartialMockingExample {

    public String methodToTest() {
        return methodToMock("input");
    }

    private String methodToMock(String input) {
        return "REAL VALUE = " + input;
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PrivatePartialMockingExampleTest {

    @Test
    public void demoPrivateMethodMocking() throws Exception {
        final String expected = "TEST VALUE";
        final String nameOfMethodToMock = "methodToMock";
        final String input = "input";

        PrivatePartialMockingExample underTest = spy(new PrivatePartialMockingExample());

        when(underTest, nameOfMethodToMock, input).thenReturn(expected);
        assertEquals(expected,underTest.methodToTest());

        verifyPrivate(underTest).invoke(nameOfMethodToMock,input);
    }
}

参考文档

http://www.importnew.com/21540.html

https://www.ibm.com/developerworks/cn/java/j-lo-powermock/index.html

https://www.cnblogs.com/softidea/p/4204389.html

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐