简体   繁体   English

ExpectedObjects 比较具体的对象,而不仅仅是接口

[英]ExpectedObjects compares concrete objects instead of only interfaces

I have an xUnit unit test project utilizing ExpectedObjects .我有一个使用ExpectedObjects的 xUnit 单元测试项目。 Right now I'm trying to test a service that returns objects conforming to an interface.现在我正在尝试测试一个返回符合接口的对象的服务。 For the sake of the question let's take an example interface为了这个问题,让我们举一个例子界面

public interface INamed
{
    string Name { get; }
}

and two different implementations:和两种不同的实现:

public class ComplexEntity : INamed
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Domain { get; set; }

    /* Many other properties follow... */
}
public class SimpleEntity : INamed
{
    public int Id { get; set; }
    public string Name { get; set; }
}

The method under test has a signature of被测方法的签名为

public IEnumerable<INamed> GetNames();

All that I care about is that the correct names are returned.我关心的只是返回正确的名称。 So after mocking whatever dependencies the service might take I construct an expected result set as follows:因此,在模拟服务可能需要的任何依赖项之后,我构建了一个预期结果集,如下所示:

IEnumerable<INamed> expected = new []
{
    new SimpleEntity { Name = "NAM1" },
    new SimpleEntity { Name = "NAM2" },
    ...
}

And I compare them as follows:我将它们进行了如下比较:

// ACT
var result = systemUnderTest.GetNames();

// ASSERT
expected.ToExpectedObject().ShouldMatch(result);

This will fail.这将失败。 The collection that is actually returned by the GetNames under test is a mixed collection of both SimpleEntities and ComplexEntities , and none will match, because the Id properties of returned objects are non-zero, and other properties from ComplexEntity that I don't care about are not even present in the expected set.被测GetNames实际返回的集合是SimpleEntitiesComplexEntities的混合集合,没有一个匹配,因为返回对象的Id属性是非零的,而我不关心的ComplexEntity其他属性甚至不存在于expected集合中。

The ExpectedObjects docs give me no guidance on this. ExpectedObjects 文档对此没有提供任何指导。 The ShouldMatch is supposed to work on anonymous types, so this should (and does) work:ShouldMatch应该在匿名类型上工作,所以这应该(并且确实)工作:

expected.Select(e => new { e.Name }).ToExpectedObject().ShouldMatch(result);

But I see this as an inflexible workaround.但我认为这是一种不灵活的解决方法。 There are many other methods with contracts including only interfaces where I don't care about the underlying types.还有许多其他带有契约的方法,仅包括我不关心底层类型的接口。 I would have to manually select the same properties from an interface in every such test and if that interface ever changed to include more properties the tests would still pass even though they wouldn't be correctly checking the contract .在每个此类测试中,我都必须从接口中手动选择相同的属性,如果该接口曾经更改为包含更多属性,则即使它们无法正确检查合同,测试仍将通过 For example if I added the Id property to INamed , the test would still pass and would never even test for Id , allowing errors to go on silently.例如,如果我将Id属性添加到INamed ,测试仍然会通过,甚至不会测试Id ,允许错误继续进行。

Is there an out-of-the-box way to make ExpectedObjects compare only the public interfaces?是否有一种开箱即用的方法可以让 ExpectedObjects 仅比较公共接口? I guess I could manually write a contrived, dynamic method creating an anonymous type out of an interface, make it generic and roll with it, something with usage like:我想我可以手动编写一个人为的动态方法,从接口创建一个匿名类型,使其通用并使用它,用法如下:

expected.ToExpectedObject().ShouldMatchInterface<INamed>(result);

but that makes me question using the library in the first place, as the effort put into ShouldMatchInterface would probably be significant.但这首先让我质疑使用该库,因为在ShouldMatchInterface付出的努力可能会很重要。

It doesn't look like you're setting up the Expected Object correctly.看起来您没有正确设置预期对象。

For example, with a GetNames() implementation like this:例如,使用这样的GetNames()实现:

public class Foo
{
    public IEnumerable<INamed> GetNames()
    {
        return new[] {
            new ComplexEntity() { Id = 1, Name = "NAM1", Domain = "DOM1" },
            new ComplexEntity() { Id = 2, Name = "NAM2", Domain = "DOM2" }
        };
    }
}

The following xUnit + ExpectedObjects test will pass:以下 xUnit + ExpectedObjects 测试将通过:

using ConsoleProject;
using ExpectedObjects;
using Xunit;

namespace ConsoleProject_Tests
{
    public class ExpectedObjectsTests
    {
        [Fact]
        public void GetNames_should_return_INamed_ExpectedObjects_Style()
        {
            var expected = new[]
            {
                new { Name = "NAM1" },
                new { Name = "NAM2" }
            }.ToExpectedObject();

            var systemUnderTest = new Foo();
            var actual = systemUnderTest.GetNames();

            expected.ShouldMatch(actual);
        }
    }
}

Note that ToExpectedObject() is being fed anonymous objects, not concrete classes.请注意, ToExpectedObject()是匿名对象,而不是具体类。

Now compare that with the old way of doing things by implementing a custom IEqualityComparer<INamed> which just happens to be on the test class...现在将它与通过实现自定义IEqualityComparer<INamed>的旧方法进行比较,该方法恰好在测试类中...

using ConsoleProject;
using System;
using System.Collections.Generic;
using Xunit;

namespace ConsoleProject_Tests
{
    public class ClassicXUnitTests : IEqualityComparer<INamed>
    {
        bool IEqualityComparer<INamed>.Equals(INamed x, INamed y)
        {
            if (x == null && y == null) return true;
            if (x == null || y == null) return false;
            return String.Equals(x.Name, y.Name);
        }

        int IEqualityComparer<INamed>.GetHashCode(INamed obj)
        {
            return obj.Name.GetHashCode();
        }

        [Fact]
        public void GetNames_should_return_INamed_xUnit_Style()
        {
            var expected = new[]
            {
                new SimpleEntity() { Name = "NAM1" },
                new SimpleEntity() { Name = "NAM2" }
            };

            var systemUnderTest = new Foo();
            var actual = systemUnderTest.GetNames();

            Assert.Equal<INamed>(expected, actual, this);
        }
    }
}

It still needs a concrete class that implements INamed because you can't just create a new abstract class or interface.它仍然需要一个实现INamed的具体类,因为您不能只创建一个新的抽象类或接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM