具有匿名对象的Arg <object> .Is.Equal

Mik*_*att 6 c# unit-testing rhino-mocks c#-4.0

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

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

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

IUrlProvider urlProvider = MockRepository.GenerateStub<IUrlProvider>();

urlProvider.Stub(u => u.Action(
    Arg<string>.Is.Equal("ValidateCode"),
    Arg<object>.Is.Equal(new { code = "spam-and-eggs" }) ))
    .Return("http://www.mysite.com/validate/spam-and-eggs");
Run Code Online (Sandbox Code Playgroud)

不幸的是,Arg<object>.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<IUrlProvider>();

urlProvider.Stub(u => u.Action(
    Arg<string>.Is.Equal("ValidateCode"),
    Arg<CodeArg>.Is.Equal(new CodeArg { code = "spam-and-eggs" }) ))
    .Return("http://www.mysite.com/validate/spam-and-eggs");
Run Code Online (Sandbox Code Playgroud)

编辑

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

我替换Arg<object>.Is.Equal()Arg<object>.Matches()使用自定义AbstractConstraint:

IUrlProvider urlProvider = MockRepository.GenerateStub<IUrlProvider>();

urlProvider.Stub(u => u.Action(
    Arg<string>.Is.Equal("ValidateCode"),
    Arg<object>.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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 1

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

所以这应该通过:

Assert.AreEqual(new { code = "spam-and-eggs" },
                new { code = "spam-and-eggs" });
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:匿名类型实例创建表达式也必须位于同一程序集中。毫无疑问,这就是本案的问题所在。

如果Equals允许您指定 an IEqualityComparer<T>,您可能可以构建一个能够比较具有相同属性的两个匿名类型的方法,方法是从另一种类型的实例的属性创建一种类型的实例,然后将其与原始类型进行比较相同类型。当然,如果您使用嵌套匿名类型,您需要递归地执行此操作,这可能会变得丑陋......