Arg.Is.Equal 与匿名对象

发布于 2024-11-08 14:53:44 字数 3201 浏览 0 评论 0原文

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

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

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

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");

不幸的是, 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<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");

编辑

如果我的单元测试与我的控制器在同一个项目中,则比较匿名对象会很好地工作。由于它们是在单独的程序集中声明的,因此即使它们具有相同的字段名称和值,它们也不会相等。比较不同命名空间中的方法创建的匿名对象似乎不是问题。

解决方案

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

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;
        }
    }
}

In my MVC3 project, I use an IUrlProvider interface to wrap the UrlHelper class. In one of my controller actions, I have a call like this:

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

I want to stub this method call in my unit test, which is in a separate project. The test setup looks something like this:

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");

Unfortunately, Arg<object>.Is.Equal(new { code = "spam-and-eggs" }) doesn't work, because new { code = "spam-and-eggs" } != new { code = "spam-and-eggs" } when the anonymous types are declared in different assemblies.

So, is there an alternative syntax I can use with Rhino Mocks to check for matching field values between anonymous objects across assemblies?

Or should I replace the anonymous object declarations with a class, like this?

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");

EDIT

If my unit test was in the same project as my controller, comparing the anonymous objects would work fine. Because they are declared in separate assemblies, they will not be equal, even if they have the same field names and values. Comparing anonymous objects created by methods in different namespaces doesn't seem to be a problem.

SOLUTION

I replaced Arg<object>.Is.Equal() with Arg<object>.Matches() using a custom 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;
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一紙繁鸢 2024-11-15 14:53:44

匿名类型以非常正常的方式实现 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,您可能可以构建一个能够通过创建一种类型的实例来比较具有相同属性的两个匿名类型的类型来自另一个实例的属性,然后将其与相同类型的原始实例进行比较。当然,如果您使用嵌套匿名类型,您需要递归地执行此操作,这可能会变得丑陋......

Anonymous types do implement Equals and GetHashCode in a pretty normal way, calling GetHashCode and Equals for each of their submembers.

So this should pass:

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

In other words, I suspect you're looking for the problem in the wrong place.

Note that you have to specify the properties in exactly the right order - so new { a = 0, b = 1 } will not be equal to new { b = 1, a = 0 }; the two objects will be of different types.

EDIT: The anonymous type instance creation expressions have to be in the same assembly, too. This is no doubt the problem in this case.

If Equals allows you to specify an IEqualityComparer<T>, you could probably build one which is able to compare two anonymous types with the same properties by creating an instance of one type from the properties of an instance of the other, and then comparing that to the original of the same type. Of course if you were using nested anonymous types you'd need to do that recursively, which could get ugly...

暮光沉寂 2024-11-15 14:53:44

由于 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;
        }
    }
}

As GetValue returns a boxed value, this appears to work correctly.

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文