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。

相关推荐
hdsoft_huge31 分钟前
SpringBoot 与 JPA 整合全解析:架构优势、应用场景、集成指南与最佳实践
java·spring boot·架构
百锦再1 小时前
详细解析 .NET 依赖注入的三种生命周期模式
java·开发语言·.net·di·注入·模式·依赖
程序员的世界你不懂2 小时前
基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(2)对框架加入业务逻辑层
java·selenium·maven
有没有没有重复的名字2 小时前
线程安全的单例模式与读者写者问题
java·开发语言·单例模式
程序员的世界你不懂4 小时前
基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(4)集成Allure报表
java·selenium·maven
isNotNullX4 小时前
数据中台架构解析:湖仓一体的实战设计
java·大数据·数据库·架构·spark
皮皮林5514 小时前
“RPC好,还是RESTful好?”,这个问题不简单
java
Xiaouuuuua4 小时前
一个简单的脚本,让pdf开启夜间模式
java·前端·pdf
车车不吃香菇5 小时前
java idea 本地debug linux服务
java·linux·intellij-idea
浩瀚星辰20246 小时前
图论基础算法:DFS、BFS、并查集与拓扑排序的Java实现
java·算法·深度优先·图论