Mockito不常用的方法

Mockito不常用的方法

在编写单元测试时,经常会使用Mockito作为mock框架。相信大家对于mock、spy、when、verify的使用已经非常熟悉。但也存在一些场景,这些常见的方法无法满足mock的场景或者verify的条件。此时,就会用到一些别的方法。

方法模拟

doAnswer

用于复杂函数的模拟

例如:方法的参数为一个lambda,此时lambda中的逻辑需要被覆盖,因此使用doAnswer执行lambda中的代码块。

方法:

java 复制代码
class Service {
    @AutoWired
    RedisTemplate<String, String> redisTemplate;

    void write() {
        redisTemplate.pipeline(connection -> {
            IntStream.range(0, 3).boxed().forEach(k 
                -> connection.hIncrBy("key" + k, "hit", 1L));
            return null;
        });
    }
}

测试方法:

java 复制代码
class ServiceTest {
    @InjectMocks
    Service service;
    @Mock
    RedisTemplate<String, String> redisTemplate;

    @Test
    void testWrite() {
        try (RedisConnection redisConnection = mock(RedisConnection.class)) {
            doAnswer(invocationOnMock -> {
            RedisCallback<Object> sessionCallback = invocationOnMock.getArgument(0, RedisCallback.class);
                sessionCallback.doInRedis(redisConnection);
                return null;
            }).when(redisTemplate).pipeline(any());
            service.writeToRedis();
            // verify
            verify(redisConnection, atLeastOnce()).hIncrBy(any(), any(), 
                longThat(new GreaterOrEqual<>(1L)));
        }
    }
}

例如:提交线程池的方法模拟,提交给真实线程池,可以模拟线程池的行为。

java 复制代码
class TaskRunnerTest {

    @Mock
    Executor executor;

    @InjectMocks
    TaskRunner taskRunner;

    Executor other = Executors.newFixedThreadPool(2);

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // 在other线程池执行
        doAnswer(invocationOnMock -> {
            Runnable runnable = invocationOnMock.getArgument(0, Runnable.class);
            executor.execute(runnable);
            return null;
        }).when(executor).execute(any());
        // 不模拟线程池行为的话,可以直接 runnable.run()
    }

    @Test
    void test() {
        // 直接执行
        taskRunner.run();
        taskRunner.run();
        // 排队
        taskRunner.run();
    }
}

例如:需要模拟延迟的情况,以触发超时或计时的逻辑。

java 复制代码
class ApiRequestTest {
    @InjectMock
    ApiRequest apiRequest;

    @Mock
    HttpHelper httpHelper;

    @Test
    void test() {
        // 模拟超时,此时响应会失败
        doAnswer(new AnswersWithDelay(100, invocationOnMock -> ""))
            .when(restHttpHelper)
            .sendGet("http://api1.com", null, String.class);
        ApiResp<String> res = apiRequst.ask("sample", null);
        assertFalse(res.isSuccess());
        // 如果配置了meter,也可以查看meter的耗时
    }
}
静态方法模拟

代码中存在一些对于静态方法的调用,此时需要对静态方法进行模拟

java 复制代码
try (MockedStatic<FileUtil> ignore2 = mockStatic(FileUtil.class)) {
      when(FileUtil.getFileRowCount(any())).thenReturn(100);
      assertTrue(pushService.exec(new File("1.txt"))
}

对象模拟

请求响应模拟

使用MockHttpServletRequest、MockHttpServletResponse:

java 复制代码
MockHttpServletResponse res = new MockHttpServletResponse();
MockHttpServletRequest req = new MockHttpServletRequest();
String logInfo = "qqq";
req.setQueryString("key=" + logInfo);
req.setParameter("key", logInfo);
Assertions.assertDoesNotThrow(() -> service.process(req, res, context));
assertTrue(response.containsHeader(HttpHeaders.CONNECTION));
Meter模拟

因为meter registry有级联调用,因此要mock它返回的Meter:

java 复制代码
class ApiServiceTest {

    @InjectMocks
    ApiService apiService;
    @Mock
    HttpHelper httpHelper;
    @Mock
    StepMeterRegistry logger;
    @Mock
    Timer timer;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        when(logger.timer("api.ask", "success", "0", "name", "sample"))
                .thenReturn(timer);
    }

    @Test
    void askDsp() {
        when(httpHelper.sendPostByte(eq("http://api.com"), 
                any(), any(), any())).thenReturn(null);
        ApiResp<String> res = apiService.ask("sample", null);
        assertFalse(res.isSuccess());
        verify(timer).record(anyLong(), eq(TimeUnit.MILLISECONDS));
    }
}
构造方法模拟

有一些类的方法中,存在一些直接构造新对象的操作,此时,需要模拟构造方法:

java 复制代码
try (MockedConstruction<Egg> jsch = mockConstruction(
            Egg.class, (mock, context) ->
                    when(mock.doSomething(anyString()).thenReturn("1"))
) {
    // test here
}

注意:File似乎无法被模拟,如果模拟File会导致整个程序错乱。

结果验证

匹配参数

ArgumentMathers类中,拥有类似下列静态方法:

java 复制代码
public static double doubleThat(ArgumentMatcher<Double> matcher) {...}
public static long longThat(ArgumentMatcher<Long> matcher) {...}

其中,真正用来验证的是ArgumentMatcher类,有一些内置的ArgumentMatcher类,例如:

java 复制代码
// 是否某个类
public static <T> T isA(Class<T> type) {
    reportMatcher(new InstanceOf(type));
    return defaultValue(type);
}

public class InstanceOf implements ArgumentMatcher<Object>, Serializable {

    private final Class<?> clazz;
    private final String description;

    public InstanceOf(Class<?> clazz) {
        this(clazz, "isA(" + clazz.getCanonicalName() + ")");
    }

    public InstanceOf(Class<?> clazz, String describedAs) {
        this.clazz = clazz;
        this.description = describedAs;
    }

    @Override
    public boolean matches(Object actual) {
        return (actual != null)
                && (Primitives.isAssignableFromWrapper(actual.getClass(), clazz)
                        || clazz.isAssignableFrom(actual.getClass()));
    }

    @Override
    public String toString() {
        return description;
    }
}
java 复制代码
public class EndsWith implements ArgumentMatcher<String>, Serializable {

    private final String suffix;

    public EndsWith(String suffix) {
        this.suffix = suffix;
    }

    @Override
    public boolean matches(String actual) {
        return actual != null && actual.endsWith(suffix);
    }

    @Override
    public String toString() {
        return "endsWith(\"" + suffix + "\")";
    }
}

使用场景:

java 复制代码
verify(service, atLeastOnce()).add(doubleThat(new GreaterOrEqual<>(1D)));
verify(service).log(isA(Context.class));

也可以自定义:

java 复制代码
verify(service, atLeastOnce()).log(argThat(context -> "1".equals(context.id));

测试基类

对于同类型的类可以用一个基类提取一些公共方法。

基类:

java 复制代码
public abstract class BaseTest<T extends BaseService> {

    @Spy
    protected Data dbData;
    @Spy
    protected XxService xxService;
    @Mock
    protected ObjectMapper objectMapper;

    @Setter
    T service;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // 初始化数据
        initData()
        // 整理数据
        dbData.organize();
        // 通用mock
        when(xxService.get("id")).thenReturn(1);
        // 初始化Json工具参数
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    protected abstract void initData();

    // 公共方法
    protected Resp getSomething(String id) {
        int id = xxService.get(id);
        return new Resp(id);
    }
}

子类

java 复制代码
public class AServiceTest extends BaseServiceTest<AService> {

    @InjectMocks
    AService aService;

    @BeforeEach
    void setUp() {
        super.setUp();
        setService(aService);
    }

    @Override
    protected void initData() {
        dbData.getBook().put("book:science", Collections.singleton(100));
        dbData.getBook().put("book:art", Collections.singleton(101));
    }

    @Test
    void test() {
        String id = service.something();
        Resp resp = getSomething(id);
        Asserstions.assertEquals("1", service.run(resp));
    }
}

无法mock的场景

多种注入方式

如果一个Bean类同时使用了构造函数注入和Field注入,则其Field无法被模拟注入。(字段注入和构造器注入同时存在Mockitio不能mock字段注入的bean_mock注入bean空指针-CSDN博客)

服务类

java 复制代码
class Service {
    @Autowired
    Helper helper;
    Data data;

    public Service(Data data) {
        this.data = data
    }
}

配置类

java 复制代码
@Configuration
class Configuration {
    @Bean
    public Service service(Data data) {
        return new Service(data);
    }
}

测试类

java 复制代码
class ServiceTest {
    @InjectMock
    Service service;
    @Mock
    Data data;
    @Mock
    Helper helper;    
}

此时无法同时模拟注入data和helper。

相关推荐
KaKa_大王7 分钟前
关于秒杀项目的一些理解
java·学习
tomla16 分钟前
使用Java VisualVM观察过期对象引用现象
java
M1A122 分钟前
Spring Boot YAML 配置读取完全指南:从基础到微服务
java
古法安卓28 分钟前
Android-SELinux 策略调试实战:从 AVC 日志到策略修复
android·java·android studio
todoitbo31 分钟前
飞算JavaAI的多租户权限隔离实测
java·springboot·ai编程·java开发·飞算javaai·java代码生成
星空31 分钟前
Springboot复习
java·spring boot·spring
Dicky-_-zhang41 分钟前
大模型部署架构:从推理引擎到弹性扩缩容的工程实践
java·jvm
vHelios1 小时前
【电商项目】商品搜索开发复盘(1):根据需求拆解搜索接口的设计逻辑
java·微服务·es
极创信息1 小时前
国产化信创适配认证高频术语:信创适配、软件自主可控、国产化率、代码溯源率、代码自主率、代码开源率是什么?
java·python·struts·eclipse·开源·php·hibernate
Data_Journal1 小时前
什么是 CAPTCHA,它是如何工作的?
java·大数据·服务器·前端·数据库