使用 xunit 对 Dictionary<string, Stream> 进行相等性检查

Equality check for Dictionary<string, Stream> with xunit

我正在尝试对一种方法进行单元测试,它使用传递给模拟方法的字典来向电子邮件添加附件。测试总是失败,逐步检查所有内容似乎都是正确的,但 Assert 似乎无法验证这一点。

一般来说,是否有一种特殊的单元测试字典的方法,它是否适用于 <string, Stream> 的设置。代码在下面,但不要认为它有什么用,但可能设置不正确,我想我遗漏了一些明显的东西。

    [Fact]
    public void Process_ShouldAttachCsvStreamWhenBuildingEmailMessage()
    {
        //Arrange
        var fixture = new Fixture();
        var settings = fixture.Create<Settings>();
        var sutFixtures = new SUTFixtures(true);
        var response = RemoteClientResponseHelper.GetMockHttpWebResponse(sutFixtures.Items);

        //deal with attachement
        var csv = sutFixtures.ToCsv();
        var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(csv);
        var messageAttachments = new Dictionary<string, Stream> {{"MissingImages.csv", new MemoryStream(bytes)}};

        var moqClientService = new Mock<IClientService>();
        moqClientService.Setup(x => x.Call(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null))
           .Returns(response.Object);

        Dictionary<string, Stream> attachmentsVerify = null;

        var moqEmailService = new Mock<IEmailService>();
        moqEmailService.Setup(
            x =>
                x.BuildMessage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
                    It.IsAny<bool>(), It.IsAny<Dictionary<string, Stream>>()))
            .Callback<string, string, string, string, bool, Dictionary<string, Stream>>(
                (to, from, subject, body, isHtml, attachments) =>
                {
                    attachmentsVerify = attachments;
                });

        //Act 
        var sut = new MissingImageNotificationStep(moqClientService.Object, moqEmailService.Object, settings);
        sut.Process(new MappingData() { Parts = sutFixtures.DataTable });

        //Assert
        moqEmailService.Verify(m => m.BuildMessage(It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<bool>(),
            It.IsAny<Dictionary<string, Stream>>()), Times.Once());

        Assert.Equal(messageAttachments, attachmentsVerify);
    }

更新

一定是我太懒了,因为我想到了一个自定义比较器,但我想也许它已经存在了。无论如何,对于我的情况,我有一些工作,看看比较器,我必须做一些明确的转换,这在我的情况下很好,但有点臭,因此我的代码需要重构,也不确定 GetHash 在这种情况下做了什么,如果这段代码在测试之外被使用,我会看看。

自定义比较器

public class DictionaryComparer : IEqualityComparer<Dictionary<string, Stream>>
{
    private readonly IEqualityComparer<Stream> _valueComparer;
    public DictionaryComparer(IEqualityComparer<Stream> valueComparer = null)
    {
        this._valueComparer = valueComparer ?? EqualityComparer<Stream>.Default;
    }

    public bool Equals(Dictionary<string, Stream> x, Dictionary<string, Stream> y)
    {
        if (x.Count != y.Count)
            return false;

        if (x.Keys.Except(y.Keys).Any())
            return false;

        if (y.Keys.Except(x.Keys).Any())
            return false;

        foreach (var pair in x)
        {
            var xValue = pair.Value as MemoryStream;
            var yValue = y[pair.Key] as MemoryStream;

            if (xValue.Length != yValue.Length)
                return false;

            xValue.Position = 0;
            yValue.Position = 0;

            var xArray = xValue.ToArray();
            var yArray = yValue.ToArray();

            return xArray.SequenceEqual(yArray);
        }

        return true;
    }

    public int GetHashCode(Dictionary<string, Stream> obj)
    {
        unchecked
        {
            var hash = 17;

            foreach (var key in obj.Keys)
            {
                hash = hash * 23 + key.GetHashCode();
            }

            return hash;
        }
    }
}

通过 XUnit 调用

Assert.Equal(messageAttachments, attachmentsVerify, new DictionaryComparer());

当前行为符合预期。由于消息附件和附件验证引用不同的对象,Assert.Equal return false.

您可以扩展 Dictionary class,并覆盖 Equals 和 GetHashCode。之后,在您的自定义词典上使用时,Assert.AreEqual 将 return 为真。

您还可以使用 xunit CollectionAsserts 来比较不同集合(在本例中为字典)的项目。

如果您想避免相等的味道,您可以创建自己的相等比较器,它只检查 public 属性(使用反射)。 Testing deep equality

正如我在评论中提到的,您可能需要覆盖 Equals 方法。 默认情况下,比较基于引用两个对象。尽管内容相同,但您的词典是不同的对象。您需要通过覆盖等于 Equals 并进行 深度 比较(内容比较)来帮助 Assert 做出决定。

至于CollectionAssert,据我所知需要相同的顺序。 因此,要么在应用 Assert 之前使用 OrderBy,要么覆盖 Equals。 通过覆盖 Equals,您将能够在代码中的任何位置比较字典。

这是一个关于如何重写该方法的示例(您可能想对字典执行相同的操作)。

public class MetricComparator : IEqualityComparer<Metric>
{
    /// <summary>
    /// Returns true if two given Metric objects should be considered equal
    /// </summary>
    public bool Equals(Metric x, Metric y)
    {
        return
            x.Source == y.Source &&
            x.Type == y.Type &&
            x.Title == y.Title &&
            x.Public == y.Public &&

            x.DayAvg == y.DayAvg &&
            x.DayMax == y.DayMax &&
            x.DayMin == y.DayMin &&

            x.HourAvg == y.HourAvg &&
            x.HourMax == y.HourMax &&
            x.HourMin == y.HourMin &&
            x.CurrentValue == y.CurrentValue;
    }
    /// <summary>
    /// Returns a hash of the given Metric object
    /// </summary>
    public int GetHashCode(Metric metric)
    {
        return 
            2 * metric.Source.GetHashCode() +
            3 * metric.Type.GetHashCode() +
            5 * metric.Title.GetHashCode() +
            7 * metric.Public.GetHashCode() +

            11 * metric.DayAvg.GetHashCode() +
            13 * metric.DayMax.GetHashCode() +
            17 * metric.DayMin.GetHashCode() +
            23 * metric.HourAvg.GetHashCode() +
            29 * metric.HourMax.GetHashCode() +
            31 * metric.HourMin.GetHashCode() +
            37 * metric.CurrentValue.GetHashCode();
    }
}