使用xunit对Dictionary <string,stream =“”>进行等式检查

时间:2017-02-20 17:24:01

标签: c# unit-testing 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());

2 个答案:

答案 0 :(得分:2)

目前的行为是预料之中的。由于消息附件和attachmentverify引用不同的对象,Assert.Equal返回false。

您可以扩展Dictionary类,并覆盖Equals和GetHashCode。之后,Assert.AreEqual将在您的自定义词典中使用时返回true。

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

如果你想避免平等气味,你可以创建自己的相等比较器,只检查公共属性(使用反射)。 Testing deep equality

答案 1 :(得分:0)

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

对于CollectionAssert,我的知识需要相同的顺序。 因此,您在应用OrderBy之前使用Assert,或覆盖Equals。 通过覆盖Equals,您可以在代码中的任何位置比较字典。

这是一个关于如何覆盖该方法的示例(您可能希望对Dictionary执行相同的操作)。

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