Modika
Modika

Reputation: 6282

Equality check for Dictionary<string, Stream> with xunit

I am trying to unit test a method, and its uses a dictionary passed to a mocked method to add attachments to an email. The test always fails, stepping through everything seems to be correct but the Assert does not seem to verify that.

Is there a special way of unit testing dictionaries in general, and would that work with on that is setup for <string, Stream>. Code is below, but don't think its anything with that, but may have set something up incorrectly, i think i am missing something obvious.

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

UPDATE

Must have been very lazy as i thought about a custom comparer but thought maybe it was already something there. I have something working, for my case anyway, looking at the comparer i have to do some explicit casting which is fine in my case but is a bit smelly and therefor my code needs a refactor, also not sure the GetHash does anything in this scenario, if this code ever gets used outside the tests i will look at that.

Custom Comparer

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

Called via XUnit

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

Upvotes: 0

Views: 7868

Answers (2)

Dmytro Bogatov
Dmytro Bogatov

Reputation: 796

As I mentioned in the comment, you may need to override Equals method. By default, comparison is based on reference two objects. Your dictionaries are different objects although with the same content. You need to help Assert decide by overriding equal Equals and doing deep comparison (content comparison).

As for CollectionAssert, it requires same order for my knowledge. So either you use OrderBy before applying Assert, or override Equals. By overriding Equals you will be able to compare Dictionaries anywhere in your code.

Here is an example on how to override that method (you may want to do the same for 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();
    }
}

Upvotes: 0

Tilak
Tilak

Reputation: 30738

The current behaviour is expected. Since message attachment and attachmentverify refer to different object, Assert.Equal return false.

You can extend Dictionary class, and override Equals and GetHashCode. After that, Assert.AreEqual will return true, when used on your custom dictionary.

You can also use xunit CollectionAsserts, to compare items of different collections (dictionary in this case).

If you want to avoid equality smell, you can create your own equality comparer that checks only public properties (using reflection). Testing deep equality

Upvotes: 4

Related Questions