.NET 8 Moq mock GetRequiredKeyedService Setup报错

代码有地方用到了IServiceProvider.GetRequiredKeyedService来解析服务,写单元测试时也需要Mock它,本以为像下面这些些就可以

var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(x => x.GetRequiredKeyedService<AAA>(It.IsAny<BBB>())).Returns(new CCC());

没想到报错了:

  Test method threw exception:
System.NotSupportedException: Unsupported expression: x => x.GetRequiredKeyedService(It.IsAny<Type>(), It.IsAny<object>())
Extension methods (here: ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService) may not be used in setup / verification expressions. Stack Trace: 
Guard.IsOverridable(MethodInfo method, Expression expression) line 87
MethodExpectation.ctor(LambdaExpression expression, MethodInfo method, IReadOnlyList`1 arguments, Boolean exactGenericTypeArguments, Boolean skipMatcherInitialization, Boolean allowNonOverridable) line 236
ExpressionExtensions.<Split>g__Split|5_0(Expression e, Expression& r, MethodExpectation& p, Boolean assignment, Boolean allowNonOverridableLastProperty) line 256
ExpressionExtensions.Split(LambdaExpression expression, Boolean allowNonOverridableLastProperty) line 170
Mock.SetupRecursive[TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) line 728
Mock.Setup(Mock mock, LambdaExpression expression, Condition condition) line 562
Mock`1.Setup[TResult](Expression`1 expression) line 645

有点奇怪,难道GetRequiredKeyedService不是接口方法?查看.NET源代码,果然,GetRequiredKeyedService是IServiceProvider的扩招方法,我们知道Moq是不支持Setup扩招方法的。

/// <summary>
/// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <param name="serviceKey">An object that specifies the key of service object to get.</param>
/// <returns>A service object of type <typeparamref name="T"/>.</returns>
/// <exception cref="System.InvalidOperationException">There is no service of type <typeparamref name="T"/>.</exception>
public static T GetRequiredKeyedService<T>(this IServiceProvider provider, object? serviceKey) where T : notnull
{
ThrowHelper.ThrowIfNull(provider); return (T)provider.GetRequiredKeyedService(typeof(T), serviceKey);
}

原因找到就好半了,翻看源码,一步步找到IServiceProvider.GetRequiredKeyedService最终调用的接口方法然后再mocke即可

首先看下requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey)调的是什么方法

  /// <summary>
/// IKeyedServiceProvider is a service provider that can be used to retrieve services using a key in addition
/// to a type.
/// </summary>
public interface IKeyedServiceProvider : IServiceProvider
{
/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <param name="serviceKey">An object that specifies the key of service object to get.</param>
/// <returns> A service object of type serviceType. -or- null if there is no service object of type serviceType.</returns>
object? GetKeyedService(Type serviceType, object? serviceKey); /// <summary>
/// Gets service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/> implementing
/// this interface.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <returns>A service object of type <paramref name="serviceType"/>.
/// Throws an exception if the <see cref="IServiceProvider"/> cannot create the object.</returns>
object GetRequiredKeyedService(Type serviceType, object? serviceKey);
}

可以看到IKeyedServiceProvider也是继承了IServiceProvider接口,这就更好办了,我们直接Mock IKeyedServiceProvider并Setup即可,将用到IServiceProvider的地方,换成IKeyedServiceProvider,代码如下:

var serviceProvider = new Mock<IKeyedServiceProvider>();
serviceProvider.Setup(x => x.GetRequiredKeyedService(It.IsAny<AAA>(), It.IsAny<BBB>())).Returns(new CCC());

运行测试,完美。

总结

解决这个问题并不困难,但是如果.Net不开源,看不到源代码,还是有点头疼。

.NET 8 Moq mock GetRequiredKeyedService Setup报错的更多相关文章

  1. cap deploy:setup报错

    今天部署cap的时候,setup出现以下错误: 查询半天未果,不过最后还是在google找到了,可见度娘极为不靠谱! I had the same error on deploy:setup with ...

  2. Mac Mojave(10.14.1)执行Matlab的mex报错

    先装了matlab2018b,发现很频繁的crash,同时考虑到要跑的代码在>=2017a时就计算错误,于是转战matlab2016b matlab2016b安装后,执行mex -setup报错 ...

  3. Selenium Grid 运行报错 Exception thrown in Navigator.Start first time ->Error forwarding the new session Empty pool of VM for setup Capabilities

    Selenium Grid 运行报错 : Exception thrown in Navigator.Start first time ->Error forwarding the new se ...

  4. python setup.py install 报错ImportError: No module named setuptools

    学习光荣之路python课程时,使用python setup.py install安装其他模块时,第一次安装某模块成功了.安装另一模块却报错ImportError: No module named s ...

  5. pip安装mysql-python报错:Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-enRreC/mysql-python/

    公司业务开发,用python开发网站;需要使用模块MySQLdb. 我直接pip install MySQLdb,当然不成功了,模块名字因该是mysql-python pip install mysq ...

  6. python pip install 报错TypeError: unsupported operand type(s) for -=: 'Retry' and 'int' Command "python setup.py egg_info" failed with error code 1 in

    pip install http://download.pytorch.org/whl/cu80/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl ...

  7. mock测试SpringMVC controller报错

    使用mock测试Controller时报错如下 java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig at org.spr ...

  8. python setup.py install 报错:error: [WinError 3] 系统找不到指定的路径。: 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\PlatformSDK\\lib

    Outline 在通过 setup.py 安装python模块时,遇到了以下报错: # 执行 python setup.py install # 报错: error: [WinError 3] 系统找 ...

  9. python setup.py install 报错

    python setup.py install 报错信息 [root@VM_25_28_centos psutil-2.0.0]# python setup.py install running in ...

  10. python下pip 安装 pyautogui报错Command "python setup.py egg_info" failed with error code 1 in C:\Users\Administrator\AppData\Local\Temp\pip-install-svhtepho\pygetwindow\

    python装的3.6 64位,使用命令pip install pyautogui 或者pip install -U pyautogui 都失败了 报错如下: Command "python ...

随机推荐

  1. RK3568J“麒麟”+“翼辉”国产系统正式发布,“鸿蒙”也正在路上!

    RK3568J "麒麟" + "翼辉"国产系统正式发布 近期,创龙科技RK3568J全国产平台(国产化率100%,提供报告)已正式适配两大国产系统:银河麒麟嵌入 ...

  2. sqlCel查询一个表中部分字段的数据后插入到另一个表中

    问题: 部门每天需要从后台系统将物流总表数据导出,Excel中整理出订单的物流发货渠道和发货时间,再手动导入到数据库中,整个过程不麻烦,但在Excel中比较繁琐. 需求: 将这个繁琐的过程变得更简单, ...

  3. Java开发常见问题分析和处理方案

    程序Bug的产生,通常分为三种类型 逻辑漏洞: 低级错误,程序执行后无法达到想要效果. 越界访问: 访问了非法区域,造成程序崩溃. 条件考虑不全面: 你以为你万无一失,但你永远都不知道输入参数究竟是什 ...

  4. Java 核心基础之static静态代码块和静态方法

    static静态代码块和静态方法 static关键字 static修饰的方法或变量,优先于对象执行,所以内存会先有static修饰的内容,后有对象的内容 可以用来修饰类的成员方法.类的成员变量,还可以 ...

  5. Spring的AOP快速实现通用日志打印

    需求分析 针对VideoService接口实现日志打印 三个核心包 spring-aop:AOP核心功能,例如代理工厂 aspectjweaver:简单理解,支持切入点表达式 aspectjrt:简单 ...

  6. Mybatis Plus 3.X版本的insert填充自增id的IdType.ID_WORKER策略源码分析

    总结/朱季谦 某天同事突然问我,你知道Mybatis Plus的insert方法,插入数据后自增id是如何自增的吗? 我愣了一下,脑海里只想到,当在POJO类的id设置一个自增策略后,例如@Table ...

  7. 解决方案 | tk.entry数字验证(输入框如何保证只能输入数字)

    from tkinter import * root = Tk() # 创建文本框 entry = Entry(root) entry.pack() # 设置文本框只能输入数字 entry.confi ...

  8. API是什么

    API就是接口,就是通道,负责一个程序和其他软件的沟通,本质是预先定义的函数.譬如我们去办事,窗口就类似一个API,如果对于某一件不简单的事情,这个窗口能做到让我们"最多跑一次", ...

  9. css3 动画插件Animate.css

    官网:https://animate.style/ GitHub:https://github.com/daneden/animate.css

  10. 固定panel1,panel2适应窗体变化

    固定panel1,panel2适应窗体变化 如果您想要固定 Panel1 并且让 Panel2 适应窗体大小的变化,可以使用以下方式设置 SplitContainer 的属性:   ' 设置 Spli ...