From our database, we querying sets of records that are put in a collection of dynamic objects of type ExpandoObject
that implements IDictionary<string, object>
on the fields. These are the actual values.
From our SpecFlow BDD tests we get a collection of TableRows that implements IDictionary<string, string>
. These are our expected values.
With FluentAssertions we'd like to test on equivalency on the whole collection with actual.Should().BeEquivalentTo(expected)
. Unfortunately, this won't work, because of the mismatch in types when the actual values are not of type string
.
We could use actual.Should().BeEquivalentTo(expected, options => options.WithAutoConversion())
, but this will make the whole actual set a collection of IDictionary<string, string>
which is not useful for comparing dates.
I assembled a testcase that will show the same issue:
var expected = new List<Dictionary<string, string>>();
expected.Add(new Dictionary<string, string>
{
{"Name", "Moon Inc."},
{"Number", "42"},
{"Date", "2018-12-31"}
});
var actual = new List<ExpandoObject>();
dynamic eo = new ExpandoObject();
eo.Name = "Moon Inc.";
eo.Number = 42;
eo.Date = new DateTime(2018, 12, 31);
actual.Add(eo);
actual.Should().BeEquivalentTo(expected, options => options);
/*
This throws:
NUnit.Framework.AssertionException:
Expected item[0][Number] to be System.String, but found System.Int32.
Expected item[0][Date] to be System.String, but found System.DateTime.
*/
actual.Should().BeEquivalentTo(expected, options => options.WithAutoConversion());
/*
This throws:
NUnit.Framework.AssertionException:
Expected item[0][Date] to be "2018-12-31" with a length of 10,
but "31-12-2018 0:00:00" has a length of 18.
/*
I tried to make the receiving type dynamic in a using method like:
actual.Should().BeEquivalentTo(expected, options => options
.Using<dynamic>(ctx => ctx.Subject.Should().Be(ctx.Expectation)).WhenTypeIs<DateTime>()
.Using<dynamic>(ctx => ctx.Subject.Should().Be(ctx.Expectation)).WhenTypeIs<int>());
/*
NUnit.Framework.AssertionException:
Expected item[0][Number] to be System.String, but found System.Int32.
Expected item[0][Date] to be System.String, but found System.DateTime.
*/
Parsing both sides to a DateTime
and using autoconversion does also not work because the actual
type is not seen as a DateTime
but as string
:
actual.Should().BeEquivalentTo(expected, options => options
.Using<dynamic>(
ctx =>
DateTime.ParseExact(ctx.Subject, "yyy-MM-dd", CultureInfo.InvariantCulture)
.Should().Be(DateTime.ParseExact(ctx.Expectation, "yyy-MM-dd", CultureInfo.InvariantCulture)))
.WhenTypeIs<DateTime>()
.WithAutoConversion());
/*
NUnit.Framework.AssertionException:
Expected item[0][Date] to be "2018-12-31" with a length of 10,
but "31-12-2018 0:00:00" has a length of 18.
*/
Is there any way with FluentAssertions to achieve this?
If nothing else helps, you can implement custom IEquivalencyStep
like this:
class WeakDateEquivalencyStep : IEquivalencyStep {
public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config) {
if (context.IsRoot)
return false;
// handles situations when subject is date
// but expectation is string
return context.Subject is DateTime && context.Expectation is string;
}
public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config) {
DateTime exp;
// we know that expection is string here
if (!DateTime.TryParse((string) context.Expectation, CultureInfo.InvariantCulture, DateTimeStyles.None, out exp)) {
// do something, your spec is invalid
throw new Exception($"Value {context.Expectation} does not represent valid date time");
}
context.Subject.Should().Be(exp, context.Because, context.BecauseArgs);
return true;
}
}
And then
actual.Should().BeEquivalentTo(expected, options =>
options.Using(new WeakDateEquivalencyStep()).WithAutoConversion());