跳至主要內容

Mockito 使用

Steven大约 2 分钟javatest

Mockito 是 Java 的模拟测试框架,通过 Mockito 可以创建和配置 Mock 对象,简化外部依赖的类的测试。

Mocikto 教程
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

初始化

注解 + ExtendWith
package org.example;

import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class Init01Test {
    @Mock // @InjectMocks —— 前者不调用实际方法,后者调用实际方法
    private UserService mockUserService;
    @Spy
    private UserService spyUserService;
    @Test
    void test() {
        Assertions.assertTrue(Mockito.mockingDetails(mockUserService).isMock());
        Assertions.assertFalse(Mockito.mockingDetails(mockUserService).isSpy()); // 💡 mock is not spy —— mock 实际为空调用,而 spy 需要监听实际方法调用,因此冲突
        Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isSpy());
        Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isMock()); // 💡 spy is mock —— 在 spy 监听实际方法调用的同时,提供 mock 改变返回值的功能!
    }
}

<!-- 提供 MockitoExtension.class 对象 -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

提示

  • Mock —— 从类型创建,不调用实际方法
  • Spy —— 对现有实例的封装,调用被封装实例的实际方法

基本使用

  • Mock —— 根据类名创建一个 “空对象”,可以对其返回值进行定制,若无定制返回默认值
  • Spy —— 根据实际对象创建一个 “代理对象”,
  • Captor —— 捕获传入的参数值
  • InjectMock —— 自动注入相关引用

mock

对于一个 mock 对象,我们可以指定返回值和执行特定的动作。 当然,也可以不指定。

如果不指定返回值的话,一个 mock 对象的所有非 void 方法都将返回默认值:

  • int、long 类型方法将返回 0
  • boolean 方法将返回 false
  • 对象方法将返回 null 等等

而 void 方法将什么都不做。

package org.example;

import org.example.entity.UserDTO;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

/**
 * 模拟返回结果/抛出异常
 */
@ExtendWith(MockitoExtension.class)
public class Mock01Test {
    @Mock
    private UserService mockUserService;
    /**
     * 测试 when 的 thenReturn 方法
     * 💡调用多个 thenReturn 时,会按顺序返回
     */
    @Test
    void test_when_thenReturn() {
        List<UserDTO> returnUserAll01 = List.of(new UserDTO());
        List<UserDTO> returnUserAll02 = List.of(new UserDTO());
        when(mockUserService.getUserAll()).thenReturn(returnUserAll01).thenReturn(returnUserAll02); // 顺序返回
        Assertions.assertArrayEquals(returnUserAll01.toArray(), mockUserService.getUserAll().toArray());
        Assertions.assertArrayEquals(returnUserAll02.toArray(), mockUserService.getUserAll().toArray());
        Assertions.assertArrayEquals(returnUserAll02.toArray(), mockUserService.getUserAll().toArray()); // 返回最后一个 thenReturn 设定
    }

    /**
     * 另一种写法
     */
    @Test
    void test_when_thenReturn_2() {
        List<UserDTO> returnUserAll01 = List.of(new UserDTO());
        doReturn(returnUserAll01).when(mockUserService).getUserAll();
        Assertions.assertArrayEquals(returnUserAll01.toArray(), mockUserService.getUserAll().toArray());
    }
    /**
     * ⚠️定义了 when 就要调用,否则抛异常 {@link UnnecessaryStubbingException}
     * ⚠️如果确定方法 mock 后不调用,一种处理方法是使用 lenient() 提供的方法进行 mock
     * 💡另一种方法是在 mock 环境建立前设置宽松的 mock 行为: Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();
     */
    @Test
    void test_UnnecessaryStubbingException_lenient() {
        // ↓ or @Mock(lenient = true)
        lenient().when(mockUserService.getUserAll()).thenReturn(new ArrayList<>()); // this won't get called
    }
    /**
     * 动态返回
     */
    @Test
    void test_thenAnswer() {
        UserDTO param = new UserDTO();
        when(mockUserService.getUserList(any(UserDTO.class))).thenAnswer((Answer<List<UserDTO>>) invocation -> {
            UserDTO argument = invocation.getArgument(0);
            return List.of(argument); // 💡运行时获取传入参数(而不是 when 时就指定)
        });
        Assertions.assertArrayEquals(List.of(param).toArray(), mockUserService.getUserList(param).toArray());
    }
    /**
     * 模拟异常抛出
     */
    @Test
    void test_doThrow() {
        doThrow(ArithmeticException.class).when(mockUserService).getUserAll();
        Assertions.assertThrows(RuntimeException.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                mockUserService.getUserAll();
            }
        });
    }
}

spy

package org.example;

import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.verification.WantedButNotInvoked;
import org.mockito.junit.jupiter.MockitoExtension;

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

import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class Spy01Test {
    @Spy
    private UserService userService;
    @Test
    void test_spy_is_mock() {
        Assertions.assertTrue(mockingDetails(userService).isMock()); // 既是 spy 也是 mock
        Assertions.assertTrue(mockingDetails(userService).isSpy());
    }

    /**
     * 测试 verify 抛出异常的情况
     */
    @Test
    void test_verify() {
        Assertions.assertThrows(WantedButNotInvoked.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                verify(userService).getUserAll(); // 校验发现方法未被调用(至少一次),则抛出异常
                // verify(userService, times(1)).getUserAll(); // 等价 ↑
            }
        });
        userService.getUserAll();
        verify(userService).getUserAll(); // 调用后再校验,则不报错
    }

    /**
     * 测试 spy 的实例与 spy 是否关联
     */
    @Test
    void test_instance_change() {
        List<String> list = new ArrayList<>();
        List<String> spy = Mockito.spy(list);
        spy.add("1");
        Assertions.assertEquals(1, spy.size());
        Assertions.assertEquals(0, list.size()); // spy 与实例并不关联,相互独立
    }
}

captor

package org.example;

import org.example.entity.UserDTO;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.*;

public @ExtendWith(MockitoExtension.class)
class Captor01Test {
    @Mock
    private UserService userService;
    @Captor
    ArgumentCaptor<UserDTO> argCaptor;
    @Test
    void test_when_capture() {
        when(userService.getUserList(argCaptor.capture())).thenReturn(null);
        UserDTO userDTO = new UserDTO();
        Assertions.assertNull(userService.getUserList(userDTO));
        Assertions.assertEquals(userDTO, argCaptor.getValue());
    }
    @Test
    void test_verify_capture() {
        UserDTO userDTO = new UserDTO();
        userService.getUserList(userDTO);
        verify(userService, times(1)).getUserList(argCaptor.capture());
        Assertions.assertEquals(userDTO, argCaptor.getValue());
    }
}

区别 @Mock/@Spy/@Captor/@InjectMock

package org.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;

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

import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;

/**
 * 区分 @Mock、@Spy、@Captor、@InjectMock
 */
@ExtendWith(MockitoExtension.class)
public class DiffTypesTest {
    @Mock
    private List<String> mock;
    @Spy
    private List<String> spyNoInstance; // 💡如果没有实例化,则退化成 mock (即不调用实际方法)
    @Spy
    private List<String> spy = new ArrayList<>();
    @Captor
    private ArgumentCaptor<String> captor; // 用于获取入参
    /**
     * 自动注入相关引用
     */
    @InjectMocks
    private InjectMockClass injectMockClass = new InjectMockClass();
    /**
     * \@Mock 不实际调用方法
     */
    @Test
    void test_mock_do_nothing() {
        mock.add("1");
        Assertions.assertEquals(0, mock.size());
    }
    /**
     * \@Spy 实际调用方法
     */
    @Test
    void test_spy_do_exactly() {
        spy.add("1");
        Assertions.assertEquals(1, spy.size());
        spyNoInstance.add("1");
        Assertions.assertEquals(0, spyNoInstance.size());
    }
    @Test
    void test_captor() {
        spy.add("1");
        verify(spy).add(captor.capture());
        Assertions.assertEquals(1, spy.size());
        Assertions.assertEquals("1", captor.getValue());
    }
    /**
     * \@InjectMock 注入相关 @Mock/@Spy 进入对象属性中
     */
    @Test
    void test_injectMock() {
        // 自动注入相关属性
        Assertions.assertEquals(mock, injectMockClass.mock);
        Assertions.assertEquals(spy, injectMockClass.spy);
        Assertions.assertFalse(mockingDetails(injectMockClass).isMock()); // 被注入属性的对象默认不是 Mock
        Assertions.assertEquals(1, injectMockClass.doSomething()); // 实际执行方法
    }
    private static class InjectMockClass {
        private List<String> mock;
        private List<String> spy;
        int doSomething() {
            return 1;
        }
    }
}

verify

通过 verify 来判断方法内部实现是否符合预想。

通过对 verify(T mock)/verify(T mock, VerificationMode mode) 的返回值的方法调用,验证某些行为是否至少发生过一次/确切的次数/从未发生过。

方法调用次数校验说明
times(int wantedNumberOfInvocations)允许验证调用的确切次数。
atLeast(int minNumberOfInvocations)允许至少 x 次调用的验证。
atMost(int maxNumberOfInvocations)允许最多 x 次调用的验证。
atLeastOnce()允许至少一次调用的验证。
atMostOnce()允许最多一次调用的验证。
never()times(0)的别名,见 times(int) 。
only()允许检查给定的方法是否只调用一次。
类是否被调用校验说明
verifyNoInteractions(Object... mocks)验证给定的 mock 对象上没有发生交互。
verifyNoMoreInteractions(Object... mocks)检查任何给定的 mock 对象上是否有任何未经验证的交互。
validateMockitoUsage()验证测试代码中是否有书写错误的地方。如是否有漏写 return、verifyMethod 等等。
package org.example;

import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.UnfinishedVerificationException;
import org.mockito.exceptions.verification.NoInteractionsWanted;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class VerifyTest {
    @Mock
    private UserService userService;
    @InjectMocks
    private MyTester myTester;
    @Test
    void test_verifyNoInteractions() {
        verifyNoInteractions(userService); // 通过,因为无调用 userService
        myTester.doSomething();
        Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                verifyNoInteractions(userService); // 异常,因为 myTester 内部调用 userService
            }
        });
        verify(userService).getUserAll();
        Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                verifyNoInteractions(userService); // 异常,因为判断的是 “是否有被调用过”
            }
        });
    }
    @Test
    void test_verifyNoMoreInteractions() {
        verifyNoMoreInteractions(userService); // 通过,因为无调用 userService
        myTester.doSomething();
        Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                verifyNoMoreInteractions(userService); // 异常,因为 myTester 内部调用 userService
            }
        });
        verify(userService).getUserAll();
        verifyNoMoreInteractions(userService); // 通过,因为判断的是 “是否没有更多异常” 了
    }
    @Test
    void test_validateMockitoUsage() {
        verify(userService);
        Assertions.assertThrowsExactly(UnfinishedVerificationException.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                validateMockitoUsage(); // 异常,因为 verify 没有方法调用
            }
        });
    }
    private static class MyTester {
        private UserService userService;
        void doSomething() {
            userService.getUserAll();
        }
    }
}

原理

todo 原理


  @Test
  public void testInit() {
    Mockito.mockingDetails(mockUserService).isMock(); // true
    Mockito.mockingDetails(spyUserService).isSpy(); // true
    Mockito.mockingDetails(spyUserService).isMock(); // true —— ❗Spy是Mock的子类
  }

问题: mock FunctionInterface 输入

todo 完善

package org.example;

import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.invocation.InvocationOnMock;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.mockito.Mockito.*;

public class Mock02FunctionInterfaceTest {
    public static class FuncHandler {
        private String pid;
        private boolean flag = false;

        public FuncHandler(String id) {
            this.pid = "p_" + id;
        }

        public FuncHandler method_01() {
            flag = true;
            return this;
        }

        public <T> T method_02(Function<String, T> func) {
            if (!flag) {
                throw new UnsupportedOperationException("please run method 01 first");
            }
            return func.apply(pid);
        }
    }

    @Test
    void testHandler() {
        assertEquals("xx_p_" + "world", new FuncHandler("world").method_01().method_02(pid -> "xx_" + pid));
        assertThrowsExactly(UnsupportedOperationException.class, () -> new FuncHandler("world").method_02(pid -> "xx_" + pid));
    }

    @Test
    void testHandlerMock() {
        try (MockedConstruction<FuncHandler> funcHandlerMockedConstruction = mockConstruction(FuncHandler.class, new MockedConstruction.MockInitializer<FuncHandler>() {
            final private AtomicInteger i = new AtomicInteger(0);
            private Object answer(InvocationOnMock invocation) throws Throwable {
                if (i.getAndIncrement() == 0) {
                    Function<String, String> arg0 = invocation.getArgument(0);
                    return arg0.apply("p_world");
                } else {
                    return invocation.callRealMethod();
                }
            }

            @Override
            public void prepare(FuncHandler mock, MockedConstruction.Context context) throws Throwable {
                when(mock.method_01()).thenCallRealMethod();
                doAnswer(this::answer).when(mock).method_02(any());
            }
        })) {
            testHandler();
        }
    }
}