表达式引用不属于模拟对象的方法
本文关键字:方法 对象 模拟 引用 不属于 表达式 | 更新日期: 2023-09-27 18:33:06
我有一个调用另一个 api 服务的 api 服务。当我设置 Mock 对象时,它失败并显示错误:
NotSupportedException:表达式引用不属于模拟对象的方法。
这是代码:
private Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>> _mockCarrierService;
private Mock<IApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
_mockApiService = new Mock<IApiService<AccountSearchModel>>();
_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
_mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
// Error occurred when call _mockApiService.GetFromApiWithQuery() in .Select()
_mockCarrierService.Setup(x => x
.Select(s => s
.GetFromApiWithQuery(It.IsAny<string>())).ToList())
.Returns(new List<IQueryable<AccountSearchModel>> { ApiValue() });
}
我阅读了带有最小起订量的表达式测试,但它对我的情况不起作用。如果我删除此_mockCarrierService.Setup()
,测试用例可以运行,但由于没有设置有效的List<IQueryable<AccountSearchModel>>
,因此NullReferenceException
失败。
知道我如何实现这一目标吗?
脚注:当前解决方案
FWIW,这是我目前使用的解决方案。我全心全意地寻求更好的解决问题的方法(直到 Moq 开始支持模拟扩展方法(。
private List<ICarrierApiService<AccountSearchModel>> _mockCarrierService;
private AccountSearchController _mockController;
private Mock<ICarrierApiService<AccountSearchModel>> _mockApiService;
[SetUp]
public void SetUp()
{
_mockApiService = new Mock<ICarrierApiService<AccountSearchModel>>();
_carrierServiceMocks = new List<ICarrierApiService<AccountSearchModel>> { _mockApiService.Object };
_mockApiService.Setup(x => x.GetFromApiWithQuery(It.IsAny<string>())).Returns(ApiValue());
_mockController = new AccountSearchController(_carrierServiceMocks);
}
脚注:替代模拟框架
我还发现了一个支持模拟扩展方法的商业模拟框架,并链接到操作文档:Telerik JustMock。
出现此问题是因为您正在尝试模拟Select
方法,该方法是扩展方法,而不是IEnumerable<T>
的实例方法。
基本上,没有办法模拟扩展方法。看看这个问题,了解一些你可能会觉得有用的想法。
UPD (12/11/2014(:
若要更深入地了解模拟扩展方法,请考虑以下事项:
- 尽管调用扩展方法就
好像它们是扩展类型的实例方法一样,但它们实际上只是一个带有一点语法糖的静态方法。
来自命名空间System.Linq
扩展方法作为纯函数实现 - 它们是确定性的,并且没有任何可观察到的副作用。我同意静态方法是邪恶的,除了那些纯函数 - 希望你也同意这个说法:)那么,给定一个类型为
T
的对象,你将如何实现静态纯函数f(T obj)
?只有通过组合为对象T
定义的其他纯函数(或实际上任何其他纯函数(,或者通过读取不可变和确定性的全局状态(以保持函数f
确定性和无副作用(来实现。实际上,"不可变和确定性的全局状态"有一个更方便的名字——一个常量。
因此,事实证明,如果您遵循静态方法应该是纯函数的规则(看起来Microsoft遵循此规则,至少对于 LINQ 方法而言(,模拟扩展方法f(this T obj)
应该可以简化为模拟该扩展方法使用的非静态方法或状态 — 仅仅是因为该扩展方法依赖于其实现中的obj
实例方法和状态(可能还依赖于其他纯函数(和/或常量值(。
在IEnumerable<T>
的情况下,Select()
扩展方法是根据foreach
语句实现的,而语句又使用GetEnumerator()
方法。因此,您可以模拟GetEnumerator()
,并为依赖于它的扩展方法实现所需的行为。
你有:
_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
所以你嘲笑IEnumerable<>
.IEnumerable<>
拥有的唯一成员是一个方法GetEnumerator()
(以及从基接口继承GetEnumerator()
具有相同签名的另一个方法(。Select
方法实际上是一个扩展方法(如第一个答案中指出的那样(,它是一种静态方法,通过调用GetEnumerator()
(可能通过 C# foreach
语句(工作。
可以通过对模拟进行Setup
GetEnumerator
来使事情正常进行。
然而,简单地使用一个具体的、非模拟的类型要简单得多,它"是"IEnumerable<>
,例如List<>
。所以试试:
_mockCarrierService = new List<ICarrierApiService<AccountSearchModel>>();
然后将条目添加到List<>
中。您应该添加的是设置GetFromApiWithQuery
方法的Mock<ICarrierApiService<AccountSearchModel>>
。
如果您需要模拟 IConfiguration,您可以使用以下代码:
var builder = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "your-key", "your value" }
});
var config = builder.Build();
由于关于嘲笑IConfiguration
的问题将这个答案称为重复,我将在这里贡献我的一角钱。这就是我"模拟"IConfiguration
的方式,在我看来,这更干净一些:
private static IConfiguration GetConfigurationMock(string jsonConfiguration)
{
var byteArray = Encoding.UTF8.GetBytes(jsonConfiguration);
var stream = new MemoryStream(byteArray);
var conf = new ConfigurationBuilder();
conf.AddJsonStream(stream);
var confRoor = conf.Build();
return confRoor;
}
原始问题:如何设置 IConfigurationRoot 的模拟以返回值