如何使用Moq配置IDispatcher的InvokeAsync方法实现单元测试回调
问题根因
- 此前配置报错的核心是委托类型不匹配,且没有正确区分
IDispatcher接口的两个InvokeAsync重载:- 非泛型重载
DispatcherOperation InvokeAsync(Action action):入参是无返回值的Action委托 - 泛型重载
DispatcherOperation<TResult> InvokeAsync<TResult>(Func<TResult> callback):入参是带返回值的Func<TResult>委托,和Action是完全不同的类型
- 非泛型重载
- 此前的错误配置里,用
It.IsAny<Func<It.IsAnyType>>()匹配的是泛型重载的入参(Func类型),但回调参数声明为Action a,委托类型不一致,Moq校验失败抛出ArgumentException。 - 另外,泛型方法无法直接通过
It.IsAnyType这类无类型模糊匹配完成Setup:Moq要求配置泛型方法时必须显式指定具体的泛型参数,否则既无法通过参数校验,也会出现Setup不生效、mock返回null的问题。
可行配置方案
分两个重载分别做Moq配置,模拟真实Dispatcher的同步执行逻辑,让测试场景下的await能直接拿到返回值:
- 第一步:配置非泛型
InvokeAsync(Action action)重载,逻辑和此前跑通的BeginInvoke配置完全一致
mockDispatcher .Setup(x => x.InvokeAsync(It.IsAny<Action>())) // 回调参数必须和方法入参类型一致,直接执行传入的Action .Callback((Action action) => action()) // 返回已完成的DispatcherOperation占位对象,避免触发跨线程调度逻辑 .Returns(() => DispatcherOperation.Completed);
- 第二步:配置泛型
InvokeAsync<TResult>(Func<TResult> callback)重载,对应实际业务中await dispatcher.InvokeAsync(() => 返回结果)的调用场景
如果测试场景中用到的泛型返回类型是明确的(比如返回自定义业务对象、值类型等),直接针对用到的泛型类型写配置即可:
// 以返回int类型的场景为例,实际使用时替换为业务中用到的返回类型 mockDispatcher .Setup(x => x.InvokeAsync<int>(It.IsAny<Func<int>>())) .Returns((Func<int> callback) => { // 直接同步执行传入的回调委托,拿到实际返回值 int result = callback(); // 包装成已完成的DispatcherOperation<int>,await时可以直接拿到结果 return DispatcherOperation<int>.FromResult(result); });
如果测试场景中涉及的泛型返回类型较多,不想逐类型写配置,可以借助Moq的宽松匹配规则,保证回调入参和方法的Func委托类型完全对应即可,绝对不能把回调参数类型写成不匹配的Action。
避坑提示:不要试图用一个Setup配置同时覆盖两个重载的入参,两个方法的委托参数类型完全独立,Moq要求每个方法重载单独做匹配配置,混写必然触发参数校验异常。
内容的提问来源于stack exchange,提问作者The Piatre
相关产品推荐
相关产品推荐

