具有匿名对象的Arg .Is.Equal

在我的MVC3项目中,我使用IUrlProvider接口来包装UrlHelper类。 在我的一个控制器操作中,我有这样的调用:

string url = _urlProvider.Action("ValidateCode", new { code = "spam-and-eggs" }); 

我想在我的unit testing中存根这个方法调用,这是在一个单独的项目中。 测试设置看起来像这样:

 IUrlProvider urlProvider = MockRepository.GenerateStub(); urlProvider.Stub(u => u.Action( Arg.Is.Equal("ValidateCode"), Arg.Is.Equal(new { code = "spam-and-eggs" }) )) .Return("http://www.mysite.com/validate/spam-and-eggs"); 

不幸的是, Arg.Is.Equal(new { code = "spam-and-eggs" })不起作用,因为new { code = "spam-and-eggs" } != new { code = "spam-and-eggs" }在不同的程序集中声明匿名类型时。

那么,是否有一种替代语法可以与Rhino Mocks一起使用来检查跨程序集的匿名对象之间的匹配字段值?

或者我应该用类替换匿名对象声明,像这样?

 public class CodeArg { public string code { get; set; } public override bool Equals(object obj) { if(obj == null || GetType() != obj.GetType()) { return false; } return code == ((CodeArg)obj).code; } public override int GetHashCode() { return code.GetHashCode(); } } string url = _urlProvider.Action("ValidateCode", new CodeArg { code = "spam-and-eggs" }); IUrlProvider urlProvider = MockRepository.GenerateStub(); urlProvider.Stub(u => u.Action( Arg.Is.Equal("ValidateCode"), Arg.Is.Equal(new CodeArg { code = "spam-and-eggs" }) )) .Return("http://www.mysite.com/validate/spam-and-eggs"); 

编辑

如果我的unit testing与我的控制器在同一个项目中,那么比较匿名对象就行了。 因为它们是在单独的程序集中声明的,所以它们不相等,即使它们具有相同的字段名称和值。 比较由不同命名空间中的方法创建的匿名对象似乎不是问题。

我使用自定义AbstractConstraint将Arg.Is.Equal()替换为Arg.Matches()

 IUrlProvider urlProvider = MockRepository.GenerateStub(); urlProvider.Stub(u => u.Action( Arg.Is.Equal("ValidateCode"), Arg.Matches(new PropertiesMatchConstraint(new { code = "spam-and-eggs" })) )) .Return("http://www.mysite.com/validate/spam-and-eggs"); public class PropertiesMatchConstraint : AbstractConstraint { private readonly object _equal; public PropertiesMatchConstraint(object obj) { _equal = obj; } public override bool Eval(object obj) { if (obj == null) { return (_equal == null); } var equalType = _equal.GetType(); var objType = obj.GetType(); foreach (var property in equalType.GetProperties()) { var otherProperty = objType.GetProperty(property.Name); if (otherProperty == null || property.GetValue(_equal, null) != otherProperty.GetValue(obj, null)) { return false; } } return true; } public override string Message { get { string str = _equal == null ? "null" : _equal.ToString(); return "equal to " + str; } } } 

匿名类型以非常正常的方式实现Equals和GetHashCode,为每个子成员调用GetHashCode和Equals。

所以这应该通过:

 Assert.AreEqual(new { code = "spam-and-eggs" }, new { code = "spam-and-eggs" }); 

换句话说,我怀疑你在错误的地方寻找问题。

请注意,您必须以完全正确的顺序指定属性 – 因此new { a = 0, b = 1 }将不等于new { b = 1, a = 0 } ; 这两个对象将是不同的类型。

编辑:匿名类型实例创建表达式也必须在同一个程序集中。 这无疑是这种情况下的问题。

如果Equals允许您指定IEqualityComparer ,则可以构建一个能够通过从另一个实例的属性创建一个类型的实例来比较两个具有相同属性的匿名类型,然后比较到相同类型的原件。 当然,如果你使用嵌套的匿名类型,你需要递归地执行,这可能会变得丑陋……

由于GetValue返回一个盒装值,这似乎可以正常工作。

 public class PropertiesMatchConstraint : AbstractConstraint { private readonly object _equal; public PropertiesMatchConstraint(object obj) { _equal = obj; } public override bool Eval(object obj) { if (obj == null) { return (_equal == null); } var equalType = _equal.GetType(); var objType = obj.GetType(); foreach (var property in equalType.GetProperties()) { var otherProperty = objType.GetProperty(property.Name); if (otherProperty == null || !_ValuesMatch(property.GetValue(_equal, null), otherProperty.GetValue(obj, null))) { return false; } } return true; } //fix for boxed conversions object:Guid != object:Guid when both values are the same guid - must call .Equals() private bool _ValuesMatch(object value, object otherValue) { if (value == otherValue) return true; //return early if (value != null) return value.Equals(otherValue); return otherValue.Equals(value); } public override string Message { get { string str = _equal == null ? "null" : _equal.ToString(); return "equal to " + str; } } }