Mock and Mockito Tutorial

Mockito Tutorial

The official address of the Mockito framework is mockito , the document address , and the Chinese version of the document .

The Mockito library can mock objects, verify results, and stubbing.

1. The relationship between Mock and Mockito

Mentioned in software development mock, it is usually understood as a mock object.

Why do we need to simulate? When we first learn programming, the objects we write are usually independent, do not depend on other classes, and will not operate other classes. But in fact, the software is full of dependencies. For example, we will write operation classes based on the service class, and the service class is based on the data access class (DAO). In turn, complex dependencies are formed.

The idea of ​​unit testing is that we want to test code without touching dependencies. This kind of testing allows you to test the validity of the code regardless of the dependencies of the code. The core idea is that if the code is working as designed, and the dependencies are working, then they should work together.

Sometimes, the dependencies required by our code may not have been developed or even exist, so how can we continue our development? The use of mock can make the development go on. The purpose and function of the mock technology is to simulate some objects that are not easy to construct or more complex in the application, so as to isolate the test from the objects outside the test boundary.

We can write a custom Mock object to implement the mock technology, but writing a custom Mock object requires additional coding work, and may also introduce errors. There are many excellent open source frameworks that implement mock technology, and Mockito is an excellent mock framework for unit testing. Mockito has been open sourced on github, please click for details: https://github.com/mockito/mockito

In addition to Mockito, there are some similar frameworks, such as:

EasyMock : An earlier popular MocK testing framework. It provides a simulation of the interface, and can complete the general testing process through three steps of recording, playback, and inspection. It can verify the type, number, and order of method calls, and can make the Mock object return a specified value or throw a specified exception.

PowerMock : This tool is extended on EasyMock and Mockito, the purpose is to solve the problems that EasyMock and Mockito cannot solve, such as static, final, and private methods cannot be mocked. In fact, code with a well-designed test architecture generally does not need these functions, but if you add unit tests to an existing project, and the old code has problems and cannot be changed, you have to use these functions

JMockit : JMockit is a lightweight mock framework that is a set of tools and APIs to help developers write test programs. The project is completely based on Java 5 SE's java.lang.instrument package development, and uses the ASM library internally to modify Java's Bytecode

Mockito has been widely used, so here we focus on Mockito.

2. Use mock objects for testing

2.1. Goals and challenges of unit testing

The idea of ​​unit testing is to test code (isolation) without involving dependencies, so the relationship between test code and other classes or systems should be eliminated as much as possible. A feasible elimination method is to replace the dependent class (test replacement), which means that we can use a substitute to replace the real dependent object.

2.2. Classification of test classes

The dummy object is passed as a parameter to the method but is never used. For example, the method inside the test class will not be called, or used to fill the parameters of a method.

Fake is the implementation body of a real interface or abstract class, but it is very simple to implement the object internally. For example, it exists in memory instead of a real database. (Translator's Note: Fake implements real logic, but it exists only for testing and is not suitable for use in products.)

The stub class is a partial method implementation of the dependent class, and these methods will be used when you test the class and interface, that is to say, the stub class will be instantiated during the test. The stub class will respond to any external test calls. The stub class sometimes records some information about the call.

A mock object refers to the mock implementation of a class or interface, and you can customize the output of a method in this object.

Test substitution techniques enable mocking of objects other than the test class in tests. So you can verify that the test class responds correctly. For example, you can verify that a method on the Mock object is called. This ensures that only the test class is tested against interference from external dependencies.

The reason we choose the Mock object is because the Mock object requires only a small amount of code for configuration.

2.3. Mock object generation

You can manually create a Mock object or use the Mock framework to simulate these classes. The Mock framework allows you to create a Mock object and define its behavior at runtime.

A typical example is to simulate a Mock object as a data provider. In the official production environment it will be implemented to connect to the data source. But when we test, the Mock object will be simulated as a data provider to ensure that our test environment is always the same.

Mock objects can be provided for testing. Therefore, the classes we test should avoid any strong dependencies on external data.

Through the Mock object or the Mock framework, we can test the expected behavior in the code. For example, verify that only a certain method of the Mock object is called.

2.4. Generate Mock objects using Mockito

Mockito is a popular mock framework that can be used in conjunction with JUnit. Mockito allows you to create and configure mock objects. Using Mockito can significantly simplify the development of externally dependent test classes.

Generally, the use of Mockito requires the following three steps

  • Mock and replace external dependencies in test code.
  • Execute the test code
  • Verify that the test code is executed correctly

By default, mockito will return the corresponding default value for all methods that return a value and have not been stubbed.

For built-in types, the default value will be returned, such as int will return 0, and boolean value will return false. For other types it will return null.

An important concept here is: the mock object will cover the entire mocked object, so the method without stub can only return the default value.

3. Notes on Mockito

3.1 @Mock and @InjectMock of mockito

Mockito is one of the most commonly used mock tools in java unit testing, providing many piling methods and annotations. There are two more commonly used annotations, @Mock and @InjectMock, whose names and positions used in the code are very similar. For beginners, it is easy to misunderstand. Let's take a moment to give a brief introduction.

Before the introduction, let’s make it clear: the two annotations @Mock and @InjectMock are completely different things without any analogy in function, except that the names and usage methods are similar.

@Mock:

Used to create mock objects in Mockito, the method of use is as follows:

@Mock
private ClassName mockedObject;

The above code creates a mock object named mockedObject of type ClassName. All methods of this object are blanked and used according to the needs of the test code logic.

@InjectMock:

This is an operation of injecting a mock object, refer to the following code:

@Mock
private ClassName mockedObject;

@InjectMock
private TestedClass TestedObj = new TestedClass();

In this code, @InjectMock declares an object to be tested. If the object has a member variable of type ClassName, the mock object defined by @Mock will be injected into the object to be tested, that is, the member of TestedObj whose type is ClassName is directly assigned as mockedObject. (Students who are familiar with dependency injection should be easy to understand)

A few points to add:

  1. @Mock creates all mock objects, that is, before piling specific methods, all properties and methods of the mock object are blanked (0 or null); corresponding to it is the @Spy annotation, @Spy can create some mock objects, and all member methods of some mock objects will be executed according to the logic of the original method until a specific value is returned by piling. @Mock and @Spy are two comparable concepts.

  2. Mokcito's mock() method has the same function as @Mock, but the usage and scenarios are different. Similarly, @Spy also corresponds to a spy() method.

  3. Objects annotated with @Mock and @Spy can be injected into the object to be processed by @InjectMock.

3.2 The difference between Mockito when(…).thenReturn(…) and doReturn(…).when(…)

There are two methods of piling (ie stub) in Mockito when(…).thenReturn(…) and doReturn(…).when(…). These two methods are interchangeable in most cases, but the results of their calls are different when Spies objects (@Spy annotations) are used instead of mock objects (@Mock annotations).
● when(...).thenReturn(...) will call the real method, if you don't want to call the real method but want to mock, don't use this method.
● doReturn(…).when(…) will not call the real method

@Test
void getUserInfoByIdTest() {
    
    
    final SysUserInfo userInfo = SysUserInfo.builder()
            .id(1L)
            .userName("admin")
            .password("123456")
            .sex(2)
            .age(99)
            .email("[email protected]")
            .createUser("admin")
            .createTime(LocalDateTime.now())
            .updateUser("admin")
            .updateTime(LocalDateTime.now())
            .build();
    Mockito.when(userInfoMapper.selectById(any())).thenReturn(userInfo);
    // 或者
    // BDDMockito.given(userInfoMapper.selectById(any())).willReturn(userInfo);
    final SysUserInfo info = userInfoService.getById(1);
    Assertions.assertNotNull(info);
}

Use the method with do when

  • stub void method
  • Stub method on spy object
  • Stub the same method multiple times to change the behavior of the mock during testing

Commonly used methods with do are doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod()

  • doReturn(): Used to have side effects when monitoring real objects and calling real methods on spy, or overwrite previous exception stubs
  • doThrow(): Used to stub void methods that require exceptions to be thrown
  • doAnswer(): Used to stub the void method and need a callback value
  • doNothing(): It is used to stub the void method without doing anything. The usage is to stub the continuous call of the void method or monitor the real object and the void method does not perform any operation
  • doCallRealMethod(): used to call the real execution method

4. Using the Mockito API

4.1. Static references

If it is statically referenced in the code org.mockito.Mockito.*;, then you can directly call static methods and static variables without creating objects, such as calling the mock() method directly.

4.2. Create and configure mock objects using Mockito

In addition to using the mock() static method mentioned above, Mockito also supports @Mockcreating mock objects through annotations.

If you use annotations, you must instantiate the mock object. When Mockito encounters annotated fields, there are three ways to initialize the mock object.

  • MockitoAnnotations.initMocks(this)
  • @RunWith(MockitoJUnitRunner.class)
  • @RuleandMockitoJUnit.rule()

@RunWith(MockitoJUnitRunner.class)Way

@RunWith(MockitoJUnitRunner.class)
public class MockitoJUnitRunnerTest{
    
    

  @Mock
  private UserDao userDao;
  
}

or

@RunWith(MockitoJUnitRunner.Silent.class)
public class MockitoJUnitRunnerTest{
    
    

  @Mock
  private UserDao userDao;
  
}

MockitoAnnotations.initMocks(this)Way

public class MockitoAnnotationsTest{
    
    
	
  @Mock
  private UserDao userDao;
  
  @Before
  public void setUp(){
    
    
    MockitoAnnotations.initMocks(this);
  }
}

@RuleandMockitoJUnit.rule()

import static org.mockito.Mockito.*;

public class MockitoTest  {
    
    

  @Mock
  MyDatabase databaseMock; //(1)

  @Rule 
  public MockitoRule mockitoRule = MockitoJUnit.rule(); //(2)

  @Test
  public void testQuery()  {
    
    
    ClassToTest t  = new ClassToTest(databaseMock); //(3)
    boolean check = t.query("* from t"); //(4)
    assertTrue(check); //(5)
    verify(databaseMock).query("* from t"); //(6)
   }
}

Or set the above step (2) to the following mode

	@Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
  1. Tell Mockito to mock the databaseMock instance
  2. Mockito creates mock objects through the @mock annotation
  3. Initialize this class with the created mock
  4. In the test environment, execute the code in the test class
  5. Use assertions to ensure that the called method returns true
  6. Verify that the query method is called by MyDatabasethe mock object

Among them, MockitoJUnitRunner has three methods

  • Silent: the implementation ignores stub parameter mismatches (MockitoJUnitRunner.StrictStubs) and does not detect unused stubs
  • Strict: detect unused stubs and report them as failed
  • StrictStubs: Improve debug tests, help keep tests clean

MockitoRule has two ways

  • silent(): will not report stub warnings during test execution
  • strictness (Strictness): Strictness levels, especially "strict stubs" (Strictness.STRICT_STUBS ) are helpful for debugging and keeping tests clean, and there are also strictness levels LENIENT and WARN

4.3. Configuring mocks

When we need to configure the return value of a method, Mockito provides a chained API for us to call conveniently

when(….).thenReturn(….)It can be used to define the return value of the function when the condition is met. If you need to define multiple return values, you can define it multiple times. When you call the function multiple times, Mockito will return the return value according to the order you defined. Mocks can also define different return values ​​according to the incoming parameters. For example, your function can take anyStringor anyIntas an input parameter, and then define its specific return value.

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

@Test
public void test1()  {
    
    
        //  创建 mock
        MyClass test = Mockito.mock(MyClass.class);

        // 自定义 getUniqueId() 的返回值
        when(test.getUniqueId()).thenReturn(43);

        // 在测试中使用mock对象
        assertEquals(test.getUniqueId(), 43);
}

// 返回多个值
@Test
public void testMoreThanOneReturnValue()  {
    
    
        Iterator i= mock(Iterator.class);
        when(i.next()).thenReturn("Mockito").thenReturn("rocks");
        String result=i.next()+" "+i.next();
        // 断言
        assertEquals("Mockito rocks", result);
}

// 如何根据输入来返回值
@Test
public void testReturnValueDependentOnMethodParameter()  {
    
    
        Comparable c= mock(Comparable.class);
        when(c.compareTo("Mockito")).thenReturn(1);
        when(c.compareTo("Eclipse")).thenReturn(2);
        // 断言
        assertEquals(1,c.compareTo("Mockito"));
}

// 如何让返回值不依赖于输入
@Test
public void testReturnValueInDependentOnMethodParameter()  {
    
    
        Comparable c= mock(Comparable.class);
        when(c.compareTo(anyInt())).thenReturn(-1);
        // 断言
        assertEquals(-1 ,c.compareTo(9));
}

// 根据参数类型来返回值
@Test
public void testReturnValueInDependentOnMethodParameter()  {
    
    
        Comparable c= mock(Comparable.class);
        when(c.compareTo(isA(Todo.class))).thenReturn(0);
        // 断言
        Todo todo = new Todo(5);
        assertEquals(todo ,c.compareTo(new Todo(1)));
}

For functions that do not return a value, we can use doReturn(…).when(…).methodCallto achieve a similar effect. For example, if we want to throw an exception when calling some functions with no return value, then we can use doThrowthe method. As shown in the code snippet below

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

// 下面测试用例描述了如何使用doThrow()方法

@Test(expected=IOException.class)
public void testForIOException() {
    
    
        // 创建并配置 mock 对象
        OutputStream mockStream = mock(OutputStream.class);
        doThrow(new IOException()).when(mockStream).close();

        // 使用 mock
        OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
        streamWriter.close();
}

4.4. Verify that the mock object method is called

Mockito will track all methods and variables in the mock object. So we can use it to verify that a function is called when a specific parameter is passed in. This method of testing is called behavioral testing. Behavioral testing does not check the return value of the function, but checks whether the function is called when the correct parameters are passed in.

import static org.mockito.Mockito.*;

@Test
public void testVerify()  {
    
    
        // 创建并配置 mock 对象
        MyClass test = Mockito.mock(MyClass.class);
        when(test.getUniqueId()).thenReturn(43);

        // 调用mock对象里面的方法并传入参数为12
        test.testing(12);
        test.getUniqueId();
        test.getUniqueId();

        // 查看在传入参数为12的时候方法是否被调用
        verify(test).testing(Matchers.eq(12));

        // 方法是否被调用两次
        verify(test, times(2)).getUniqueId();

        // 其他用来验证函数是否被调用的方法
        verify(mock, never()).someMethod("never called");
        verify(mock, atLeastOnce()).someMethod("called at least once");
        verify(mock, atLeast(2)).someMethod("called at least twice");
        verify(mock, times(5)).someMethod("called five times");
        verify(mock, atMost(3)).someMethod("called at most 3 times");
}

4.5. Use Spy to encapsulate java objects

@Spy or spy()methods can be used to encapsulate java objects. After being encapsulated, unless there is a special statement (pile stub ), every method in the object will actually be called

import static org.mockito.Mockito.*;

// Lets mock a LinkedList
List list = new LinkedList();
List spy = spy(list);

// 可用 doReturn() 来打桩
doReturn("foo").when(spy).get(0);

// 下面代码不生效
// 真正的方法会被调用
// 将会抛出 IndexOutOfBoundsException 的异常,因为 List 为空
when(spy.get(0)).thenReturn("foo");

method verifyNoMoreInteractions()allows you to check that no other methods have been called.

4.6. Dependency Injection in Mockito with @InjectMocks

We can also use @InjectMocksannotations to create objects, which will inject member methods and variables in the object according to the type. Simply put, this Mock can call the method of the real code, and the rest of the mocks created with the @Mock (or @Spy) annotation will be injected into the instance. You can call the real method in the class. Suppose we have ArticleManager class

public class ArticleManager {
    
    
    private User user;
    private ArticleDatabase database;

    ArticleManager(User user) {
    
    
     this.user = user;
    }

    void setDatabase(ArticleDatabase database) {
    
     }
}

This class will be constructed by Mockito, and the member methods and variables of the class will be replaced by mock objects, as shown in the following code snippet:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest  {
    
    

       @Mock ArticleCalculator calculator;
       @Mock ArticleDatabase database;
       @Most User user;

       @Spy private UserProvider userProvider = new ConsumerUserProvider();

       @InjectMocks private ArticleManager manager; (1)

       @Test public void shouldDoSomething() {
    
    
               // 假定 ArticleManager 有一个叫 initialize() 的方法被调用了
               // 使用 ArticleListener 来调用 addListener 方法
               manager.initialize();

               // 验证 addListener 方法被调用
               verify(database).addListener(any(ArticleListener.class));
       }
}
  1. Create an instance of ArticleManager and inject the Mock object

More details can be found at
http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html .

4.7. Capturing parameters

ArgumentCaptorClasses allow us to access method parameters during verification. After getting the parameters of the method we can use it for testing.

import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.Arrays;
import java.util.List;

import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

public class MockitoTests {
    
    

        @Rule public MockitoRule rule = MockitoJUnit.rule();

        @Captor
    private ArgumentCaptor> captor;

        @Test
    public final void shouldContainCertainListItem() {
    
    
        List asList = Arrays.asList("someElement_test", "someElement");
        final List mockedList = mock(List.class);
        mockedList.addAll(asList);

        verify(mockedList).addAll(captor.capture());
        final List capturedArgument = captor.>getValue();
        assertThat(capturedArgument, hasItem("someElement"));
    }
}

4.8. Limitations of Mockito

Of course, Mockito also has certain limitations. The following three data types cannot be tested

  • final classes
  • anonymous classes
  • primitive types

Guess you like

Origin blog.csdn.net/ToBeMaybe_/article/details/115703754
Recommended