Skip to content

Custom argument equality

Default behavior when comparing argument values

By default, FakeItEasy compares argument values using Object.Equals. For instance, consider this call configuration:

A.CallTo(() => fake.DoSomething("hello")).Returns(42);

When comparing the argument value from the actual call with the configured value "hello", the values are compared with the default comparison rules (using String.Equals in this case).

In most cases, that's what you want, but there are scenarios where it can be inconvenient. For example, you might want to compare instances based on the values of their properties, but by default the objects are compared using reference equality. If you don't own the type, you can't override Equals to implement the desired behavior. So you end up having to compare the properties explicitly in an argument constraint:

Foo expectedFoo = ...;
A.CallTo(() =>
    fake.DoSomethingElse(A<Foo>.That.Matches(foo => foo.Bar == expectedFoo.Bar)))
    .Returns(42);

This is quite verbose, and not very readable. Ideally you would write it like this:

Foo expectedFoo = ...;
A.CallTo(() => fake.DoSomethingElse(expectedFoo)).Returns(42);

And it would just do the right thing.

FakeItEasy offers a way to override the default behavior by providing a custom argument equality comparer.

Writing a custom argument equality comparer

Just define a class that inherits ArgumentEqualityComparer<T>, and override the AreEqual method:

public class FooComparer : ArgumentEqualityComparer<Foo>
{
    protected override bool AreEqual(Foo expectedValue, Foo argumentValue)
    {
        return expectedValue.Bar == argumentValue.Bar;
    }
}

FakeItEasy will automatically discover this class and use it to compare instances of Foo.

How it works

FakeItEasy uses classes that implement the following interface to compare argument values:

public interface IArgumentEqualityComparer
{
    bool CanCompare(Type type);
    bool AreEqual(object expectedValue, object argumentValue);
    Priority Priority { get; }
}

When FakeItEasy needs to compare a non-null argument value with a non-null expected value, it looks at all known IArgumentEqualityComparer implementations for which CanCompare returns true for the type of the expected value. If multiple implementations match, the one with the highest Priority is used.

If all that's needed is an Argument Equality Comparer that specifies how to compare two instances of a specific type, extending abstract class ArgumentEqualityComparer<T>: IArgumentEqualityComparer is preferred. It provides default implementations of Priority and CanCompare (although they can be overridden if needed).

However, if you want to provide custom equality comparison for a variety of types, you may prefer to implement IArgumentEqualityComparer directly. For example, if you wanted all types in a specific namespace to be compared by their string representation, you might write something like this:

class ToStringArgumentEqualityComparer : IArgumentEqualityComparer
{
    public bool CanCompare(Type type) => type.Namespace == "MySpecialNamespace";

    public bool AreEqual(object expectedValue, object argumentValue)
    {
        return expectedValue.ToString() == argumentValue.ToString();
    }

    public Priority Priority => Priority.Default;
}

How does FakeItEasy find Argument Equality Comparers?

On initialization, FakeItEasy looks for Discoverable Extension Points, including Argument Equality Comparers.