我有一个api服务调用另一个api服务。当我设置Mock对象时,它失败并出现错误:
NotSupportedException:expression引用一个不属于模拟对象的方法。
这是代码:
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() });
}
我看了Expression testing with Moq,但它对我的案子没有用。如果我删除此_mockCarrierService.Setup()
,则测试用例可以运行,但由于NullReferenceException
设置失败而导致失败,因为它没有设置List<IQueryable<AccountSearchModel>>
。
知道如何实现这一目标吗?
脚注:当前解决方案
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。
答案 0 :(得分:63)
出现此问题的原因是您尝试模拟Select
方法,extension method,而不是IEnumerable<T>
的实例方法。
基本上,没有办法模拟扩展方法。请查看this question,了解您可能会觉得有用的一些想法。
UPD(12/11/2014):
要了解有关模拟扩展方法的更多信息,请考虑以下事项:
虽然扩展方法被称为扩展类型的实例方法,但它们实际上只是一个带有一点语法糖的静态方法。
System.Linq
命名空间中的扩展方法实现为pure functions - 它们是确定性的,并且没有任何可观察的side effects。我同意static methods are evil, except those that are pure functions - 希望你也同意这句话:)
因此,给定T
类型的对象,您将如何实现静态纯函数f(T obj)
?只有通过组合为对象T
(或任何其他纯函数,实际上)定义的其他纯函数,或者通过读取不可变和确定性全局状态(以保持函数f
确定性和侧面 - 效果免费)。实际上,“不可变和确定性的全局状态”具有更方便的名称 - 常量。
因此,事实证明,如果您遵循静态方法应该是纯函数的规则(并且看起来Microsoft遵循此规则,至少对于LINQ方法),则模拟扩展方法f(this T obj)
应该可以简化为模拟非扩展方法所使用的非静态方法或状态 - 只是因为该扩展方法在其实现中依赖于obj
实例方法和状态(可能还有其他纯函数)和/或常数值。)
如果是IEnumerable<T>
,Select()
扩展方法foreach
就GetEnumerator()
语句而言,GetEnumerator()
语句依次使用{{1}}方法。因此,您可以模拟{{1}}并为依赖它的扩展方法实现所需的行为。
答案 1 :(得分:7)
你有:
_mockCarrierService = new Mock<IEnumerable<ICarrierApiService<AccountSearchModel>>>();
所以你嘲笑IEnumerable<>
。唯一的成员IEnumerable<>
有一个方法GetEnumerator()
(加上另一个具有从基接口继承的相同签名GetEnumerator()
的方法)。 Select
方法实际上是一种扩展方法(如第一个答案中所指出的),它是一种静态方法,通过调用GetEnumerator()
(可能通过C#foreach
语句)来工作。
可以通过模拟Setup
GetEnumerator
来使事情有效。
但是,简单地使用“is”IEnumerable<>
的具体非模拟类型(例如List<>
)要简单得多。所以试试:
_mockCarrierService = new List<ICarrierApiService<AccountSearchModel>>();
然后在List<>
中添加一个条目。您应该添加的内容是Mock<ICarrierApiService<AccountSearchModel>>
,其中GetFromApiWithQuery
方法已设置。
答案 2 :(得分:0)
如果您需要模拟IConfiguration,也可以在下面使用此代码:
var builder = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "your-key", "your value" }
});
var config = builder.Build();
答案 3 :(得分:0)
由于关于嘲笑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;
}
原始问题:How to setup Mock of IConfigurationRoot to return value