Moq使用匿名类型参数进行验证



我有下面的测试和支持类,但我不知道如何验证对依赖项的调用。

[TestFixture]
public class AnonymousGenericTypeParameterTests
{
[Test]
public void Test()
{
// Arrange
var dependency = new Mock<IDependency>();
var towns = new List<Town>
{
new Town { Name = "Lifford", County = "Donegal", Country="Ireland", Population = 1658 },
new Town { Name = "Ballyshannon", County = "Donegal", Country="Ireland", Population = 2504 },
new Town { Name = "Buxton", County = "Derbyshire", Country="United Kingdom", Population = 13599 },
};
var sut = new MyClass(dependency.Object);
// Act
sut.DoSomething(towns);
// Assert
// The following line needs to be fixed.
dependency.Verify(d => d.Execute(It.IsAny<IEnumerable<object>>(), It.IsAny<Func<object, decimal?>>()));
}
}
public interface IDependency
{
void Execute<T>(IEnumerable<T> collection, Func<T, decimal?> rateSelector);
}
public class MyClass
{
private readonly IDependency dependency;
public MyClass(IDependency dependency)
{
this.dependency = dependency;
}
public void DoSomething(IEnumerable<Town> towns)
{
var counties = towns.GroupBy(t => new {t.Country,t.County});
foreach (var county in counties)
{
dependency.Execute(county, c => c.Population);
}
}
}
public class Town
{
public string Name { get; set; }
public string County { get; set; }
public int Population { get; set; }
public string Country { get; set; }
}

根据Moq的测试输出,执行的调用为:

Dependency.Execute(System.Linq.Lookup`2+Grouping[<>f__AnonymousType0`2[System.String,System.String],UniFocus.Staffscope4.Tests.Town], System.Func`2[UniFocus.Staffscope4.Tests.Town,System.Nullable`1[System.Decimal]])

我在Moq中看到了很多关于匿名参数的问题(比如this、this和this),但找不到任何关于使用匿名类型作为实际类型参数的问题。

可以在Verify行中放入什么,以便它实际验证内部的调用?

注意:我的示例IDependency没有返回值(我认为它已经足够复杂了),但对于隐含或显式处理Setup()Verify()的答案,会有额外的赞誉。

更新Jesse的解决方案只通过了测试,因为我在制作示例时做了一个糟糕的选择。我应该意识到,任何IGrouping<out TKey, out TElement>也是IEnumerable<TElement>。有更普遍的解决方案吗?

更新2我觉得我原来的例子可能太复杂了,不能很好地代表我问题的实际标题。对于这个更直接、更切中要害的例子,有什么解决方案吗?

using Moq;
using NUnit.Framework;
namespace Tests
{
[TestFixture]
public class SimpleAnonymousGenericTypeParameterTests
{
[Test]
public void Test()
{
// Arrange
var dependency = new Mock<IDependency>();
var sut = new MyClass(dependency.Object);
// Act
sut.DoSomething("Donegal", "Lifford");
// Assert
// This verify works for both calls to Execute()
dependency.Verify(d => d.Execute(It.IsAny<object>()), Times.Exactly(2));
// This verify should specifically refer to only the first call to Execute()
dependency.Verify(d => d.Execute(It.IsAny</*HowToRepresentAnonymousTypeHere*/object>()), Times.Once);
}
public interface IDependency
{
void Execute<T>(T thing);
}
public class MyClass
{
private readonly IDependency dependency;
public MyClass(IDependency dependency)
{
this.dependency = dependency;
}
public void DoSomething(string county, string town)
{
dependency.Execute(new { county, town });
object someUnknownObject = "";
dependency.Execute(someUnknownObject);
}
}
}
}

接受的答案对我不起作用,我相信这是因为测试和有问题的对象在不同的程序集中,所以Moq不知道如何协调类型,也不匹配它们。

相反,我创建了以下辅助方法,可以验证提供的匿名类型是否具有正确的字段和值:

public static class AnonHelpers
{
public static object MatchAnonymousType(object expected)
{
return Match.Create(Matcher(expected));
}
private static Predicate<object> Matcher(object expected)
{
return actual =>
{
var expectedProp = expected.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(expected));
var actualProp = actual.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(actual));
foreach (var prop in expectedProp)
{
if (!actualProp.ContainsKey(prop.Key))
return false;
if (!prop.Value.Equals(actualProp[prop.Key]))
return false;
}
return true;
};
}
}

它们可以这样使用:

var anon = new { SomeKey = "some value", SomeOtherKey = 123 };
myMock.Setup(x => x.MyMethod(personIDs, AnonHelpers.MatchAnonymousType(anon))).Verifiable();

这将创建一个匹配器,该匹配器将根据匿名类型的键和值使用反射来匹配匿名类型,然后您可以使用常规验证来查看何时调用了它。

由于类型在测试上下文中是已知的,因此可以为Verify调用提供特定的类型参数。以下更改使测试通过:

dependency.Verify(d =>
d.Execute(It.IsAny<IEnumerable<Town>>(), It.IsAny<Func<Town, decimal?>>()));

这同样适用于设置。

关于Update2中的示例,通过了以下步骤,但它需要了解DoSomething()方法的内部工作原理,据我所知,这是使其工作的唯一方法:

var anonymousType = new {county = "Donegal", town = "Lifford"};
dependency.Verify(d => d.Execute(anonymousType), Times.Once);

相关内容

最新更新