C# Fluent Assertions global options for ShouldBeEquivalentTo
Asked Answered
P

3

22

In Fluent Assertions when comparing objects with DateTime properties there are sometimes a slight mismatch in the milliseconds and the comparison fail. The way we get around it is to set the comparison option like so:

actual.ShouldBeEquivalentTo(expected,
        options =>
            options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation))
                .WhenTypeIs<DateTime>());

Is there a way to set this up once and have it always apply instead of having to specify it every time we call ShouldBeEquivalentTo?

Update1: Tried the following approach but it doesn't seem to work, test fails on 1 millisecond difference. The new default does not seem to get called by the factory.

using System;
using FluentAssertions;
using FluentAssertions.Equivalency;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject1
{
    class Test
    {
        public DateTime TestDateTime { get; set; }
    }

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void SettingFluentAssertionDefault()
        {
            // arrange
            var defaultAssertionOptions = EquivalencyAssertionOptions<DateTime>.Default;

            EquivalencyAssertionOptions<DateTime>.Default = () =>
            {
                var config = defaultAssertionOptions();
                config.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>();
                return config;
            };

            var testDateTime = DateTime.Now;
            var expected = new Test {TestDateTime = testDateTime};

            // act
            var actual = new Test {TestDateTime = testDateTime.AddMilliseconds(1)};

            // assert
            actual.ShouldBeEquivalentTo(expected);
        }
    }
}
Pantheas answered 19/12, 2013 at 23:45 Comment(2)
Is the TestInitialize itself called?Astrogeology
Yes, TestInitialize is called. I even added the code snippet directly to the test before I perform the check and it doesn't seem to get hit. Updating the question with a complete test unit.Pantheas
A
20

Actually, you can. The default configuration factory is exposed by the static property EquivalencyAssertionOptions<Test>.Default. You can easily assign an alternative configuration for a particular data type, or extend the default configuration with additional behavior. Something like:

var defaultAssertionOptions = EquivalencyAssertionOptions<Test>.Default;

EquivalencyAssertionOptions<Test>.Default = () =>
{
    var config = defaultAssertionOptions();
    config.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>();
    return config;
};

If you want you can get the current default and tuck that away in some variable that you use from your factory method.

Astrogeology answered 20/12, 2013 at 12:6 Comment(3)
Maybe I am doing it wrong but I tried adding the following to my test initialize but it doesn't seem to get called when the factory is instantiated. EquivalencyAssertionOptions<DateTime>.Default = () => { var config = defaultAssertionOptions(); config.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>(); return config; };Pantheas
Also, the proposed code doesn't compile because the constructor for EquivalencyAssertionOptions is private.Pantheas
Actually, my example was wrong. Since you're executing an assertion on an instance of Test class, you must override the default options for that specific class and not the DateTime that is used internally. I've just tested this using your example and updated my original answer.Astrogeology
I
27

Now this can be done with the AssertionOptions static class. To use a simple example:

[TestInitialize]
public void TestInit() {
  AssertionOptions.AssertEquivalencyUsing(options => options.ExcludingMissingMembers());
}

Or as in the example above:

AssertionOptions.AssertEquivalencyUsing(options =>
  options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>()
);
Intestine answered 19/3, 2015 at 23:4 Comment(1)
Just in case anyone is as stupid as me - bear in mind that being static, these options persist throughout the entire test run (which was of course the O.P.'s request). I had a test which set the options to ignore equivalency comparison on properties with a certain name, assuming that this was being reassigned in each test. It wasn't, it was adding a new rule every time, so each test used the previous test's 'ignore list' as well as its own. Lesson learnt....Allotropy
A
20

Actually, you can. The default configuration factory is exposed by the static property EquivalencyAssertionOptions<Test>.Default. You can easily assign an alternative configuration for a particular data type, or extend the default configuration with additional behavior. Something like:

var defaultAssertionOptions = EquivalencyAssertionOptions<Test>.Default;

EquivalencyAssertionOptions<Test>.Default = () =>
{
    var config = defaultAssertionOptions();
    config.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>();
    return config;
};

If you want you can get the current default and tuck that away in some variable that you use from your factory method.

Astrogeology answered 20/12, 2013 at 12:6 Comment(3)
Maybe I am doing it wrong but I tried adding the following to my test initialize but it doesn't seem to get called when the factory is instantiated. EquivalencyAssertionOptions<DateTime>.Default = () => { var config = defaultAssertionOptions(); config.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation)).WhenTypeIs<DateTime>(); return config; };Pantheas
Also, the proposed code doesn't compile because the constructor for EquivalencyAssertionOptions is private.Pantheas
Actually, my example was wrong. Since you're executing an assertion on an instance of Test class, you must override the default options for that specific class and not the DateTime that is used internally. I've just tested this using your example and updated my original answer.Astrogeology
L
0

I am afraid the closest thing you can come to, is providing new methods

public static void ShouldBeEquivalentToDef<T>(this T subject, object expectation, string reason = "",
    params object[] reasonArgs)
{
    ShouldBeEquivalentToDef(subject, expectation, config => config, reason, reasonArgs);
}

public static void ShouldBeEquivalentToDef<T>(this T subject, object expectation,
    Func<EquivalencyAssertionOptions<T>, EquivalencyAssertionOptions<T>> config, string reason = "", params object[] reasonArgs)
{
    var context = new EquivalencyValidationContext
    {
        Subject = subject,
        Expectation = expectation,
        CompileTimeType = typeof (T),
        Reason = reason,
        ReasonArgs = reasonArgs
    };

    var defConstructedOptions = config(EquivalencyAssertionOptions<T>.Default());
    defConstructedOptions.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation))
            .WhenTypeIs<DateTime>()

    new EquivalencyValidator(defConstructedOptions).AssertEquality(context);
}
Lourielouse answered 20/12, 2013 at 0:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.