Reputation: 6282
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
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
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