Idioms

Over time developers have agreed on certain best practices. Writing tests to ensure those best practices are followed through can be tedious.

Based on AutoFixture, the classes contained in the package AutoFixture.Idioms help creating unit tests that quickly check if these best practices are properly followed.

The AutoFixture.Idioms package contains several assertions. Each assertion encapsulates a unit test that tests a specific expectation on the system under test.

All assertions implement the interface IIdiomaticAssertion. This interface exposes a plethora of overloads of the Verify methods accepting everything from a set of assemblies down to a single member.

The abstract class IdiomaticAssertion offers a basic implementation of all overloads but the ones at the end of the tree (constructors, methods, properties, fields).

Given how the IdiomaticAssertion class works, developers can target a specific member or the whole type. It will be up to the author of the assertion to make sure that only the suitable members are tested.

var fixture = new Fixture();
var assertion = fixture.Create<MyFakeAssertion>();
assertion.Verify(typeof(TestClass));
assertion.Verify(typeof(TestClass).GetMethods());
assertion.Verify(typeof(TestClass).GetMethod(nameof(TestClass.TestMethod)));

Here is a list of scenarios that can be accellerated by using assertions available in the AutoFixture.Idioms package.

Guard a method against null values

When writing methods or constructors, it is good practice to protect them against unsupported null values. The GuardClauseAssertion verifies that the parameters passed to a method or a constructor are properly checked against null values.

Assuming a test class like the following one (for semplicity we will use string as dependencies):

public class TestClass
{
private readonly string _firstDependency;
private readonly string _secondDependency;
public TestClass(string firstDependency, string secondDependency)
{
_firstDependency = firstDependency ?? throw new ArgumentNullException(nameof(firstDependency));
_secondDependency = secondDependency ?? throw new ArgumentNullException(nameof(secondDependency));
}
public void DoSomething(string parameter)
{
if (parameter == null) throw new ArgumentNullException(nameof(parameter));
...
}
}

The snippet below will test that every parameter of the constructor is guarded against null values.

[Test]
public void Constructor_is_guarded_against_nulls()
{
// ARRANGE
var fixture = new Fixture();
var assertion = fixture.Create<GuardClauseAssertion>();
// ACT & ASSERT
assertion.Verify(typeof(TestClass).GetConstructors());
}

If any of the nullable parameters of the constructor were not to be guarded, an exception would be thrown and the unit test would fail.

To be noted that without the help of the Idioms package, the developer would expected to write N+1 unit tests, only for the constructor: one for each incoming parameter and one for the correct initialization of the system under test.

[Test]
public void FirstDependency_is_required()
{
// ARRANGE
var fixture = new Fixture();
// ACT & ASSERT
Assert.That(() => new TestClass(null, fixture.Create<string>()), Throws.ArgumentNullException);
}
[Test]
public void SecondDependency_is_required()
{
// ARRANGE
var fixture = new Fixture();
// ACT & ASSERT
Assert.That(() => new TestClass(fixture.Create<string>(), null), Throws.ArgumentNullException);
}
[Test]
public void TestClass_can_be_instantiated()
{
// ARRANGE
var fixture = new Fixture();
// ACT & ASSERT
Assert.That(() => new TestClass(fixture.Create<string>(), fixture.Create<string>()), Throws.Nothing);
}

Besides the annoyance of creating multiple repetitive tests, the tests above will need to be fixed every time a new change in the constructor were to occur.

The same assertion can be used to verify the correct implementation of normal methods.

[Test]
public void DoSomething_is_guarded_against_nulls()
{
// ARRANGE
var fixture = new Fixture();
var assertion = fixture.Create<GuardClauseAssertion>();
// ACT & ASSERT
assertion.Verify(typeof(TestClass).GetMethod(nameof(TestClass.DoSomething)));
}

Constructor initialization

Another good practice is to initialize read-only properties with values passed to the constructor. The ConstructorInitializedMemberAssertion can be used to verify that these properties have been initialized with the value passed to the constructor via same-name arguments.

Let´s take this class as test subject.

public class TestClass
{
public TestClass(string parameter, int value)
{
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
Value = value;
}
public string Parameter { get; }
public int Value { get; }
}

The ConstructorInitializedMemberAssertion can be used to verify that the properties Parameter and Value contain the same values passed to the constructor.

[Test]
public void Properties_are_initialized_by_constructor()
{
// ARRANGE
var fixture = new Fixture();
var assertion = fixture.Create<ConstructorInitializedMemberAssertion>();
// ACT & ASSERT
assertion.Verify(typeof(TestClass));
}

Commenting any of the two assignments in the TestClass constructor will cause the test in the snippet above to fail.

Value equality

When working with value objects (i.e. objects who are distinguishable by the state of their properties and not by their identity), handling correctly the equality checks is paramount.

In C#, equality checks are customized by overriding the virtual methods Object.Equals and Object.GetHashCode.

When overriding thesem methods, developers must make sure that the properties of equality are satisfied. These are:

  • Reflexive: an object must be equal to itself. a == a

  • Symmetric: if an object is equal to another, the second is also equal to the first. (a == b) => (b == a)

  • Transitive: if an object is equal to another, and this is equal to a third, the first is equal to the third. (a == b, b == c) => (a == c)

On top of these logic requirements, there are additional requirements:

  • A reference type cannot be equal to null,

  • A class overriding Object.Equals must override Object.GetHashCode too,

  • Applying repetedly Object.Equals to the same object must return the same result,

  • Applying repetedly Object.GetHashCode to the same object must return the same result,

  • Testing for equality with new object() must return false.

Unfortunately, as of today, C# doesn't have a good built-in support for value objects (see Records coming in C# 9.0). This means that a lot of work has to be put to correctly handle value equality.

AutoFixture.Idioms contains assertions that can reduce the amount of code needed for the tests.

Specifically, these assertions are available:

  • EqualsNewObjectAssertion verifies that Object.Equals is implemented so that x.Equals(new object()) is always false,

  • EqualsNullAssertion verifies that Object.Equals is implemented so that x.Equals(null)) is always false,

  • EqualsSelfAssertion verifies that Object.Equals is implemented so that x.Equals(x)) is always true,

  • EqualsSuccessiveAssertion verifies that Object.Equals is implemented so that calling x.Equals(y) several times returns always the same value,

  • GetHashCodeSuccessiveAssertion verifies that Object.GetHashCode is implemented so that calling x.GetHashCode() several times returns always the same value.

As of today, developers are left to write unit tests that prove that the symmetric and transitive properties are respected but they can be found in this package. Since this package is based on AutoFixture 3.36, it might not work with newer versions but developers can take inspiration from its source code.

Since most likely all the equality assertions needs to be checked, these can be combined into a single one specializing the CompositeIdiomaticAssertion abstract class.

Let's consider this class as example

public class SampleValueObject
{
public string StringValue { get; set; }
public int IntValue { get; set; }
public override bool Equals(object obj)
{
if (obj is SampleValueObject other)
{
return string.Equals(StringValue, other.StringValue, StringComparison.Ordinal) && Equals(IntValue, other.IntValue);
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(typeof(SampleValueObject), StringValue);
}
}

We can define our own EqualityAssertion as follows

public class EqualityAssertion : CompositeIdiomaticAssertion
{
public EqualityAssertion(ISpecimenBuilder builder) : base(CreateChildrenAssertions(builder)) { }
private static IEnumerable<IIdiomaticAssertion> CreateChildrenAssertions(ISpecimenBuilder builder)
{
yield return new EqualsNewObjectAssertion(builder);
yield return new EqualsNewObjectAssertion(builder);
yield return new EqualsSelfAssertion(builder);
yield return new EqualsSuccessiveAssertion(builder);
yield return new GetHashCodeSuccessiveAssertion(builder);
}
}

We can then create a simple unit test like the one below

[Test]
public void Equality_is_correctly_implemented()
{
// ARRANGE
var fixture = new Fixture();
var assertion = fixture.Create<EqualityAssertion>();
// ACT & ASSERT
assertion.Verify(typeof(SampleValueObject));
}

The main advantage of this approach is that developers can later on expand the EqualityAssertion class by addition additional child assertions.

Equality comparers

When it's not possible to customize the behavior of the class, or simply it's preferrable to have multiple equality comparison strategies (like the case of the different string comparers), developers can create their own comparers by implementing the interface IEqualityComparer<T>.

This interface exposes two methods, bool Equals(T,T) and int GetHashCode(T), and the same rules applying for value equality need to be followed when implementing this interface.

Starting from version 4.14.0, AutoFixture includes a EqualityComparerAssertion that can be used to validate the implementation of the custom equality comparer.

Let's consider the following equality comparer for the class SampleValueObject displayed above

public class SampleValueObjectEqualityComparer : IEqualityComparer<SampleValueObject>
{
public bool Equals(SampleValueObject x, SampleValueObject y)
{
if (x is null && y is null) return true;
if (x is null ^ y is null) return false;
return string.Equals(x.StringValue, y.StringValue) && Equals(x.IntValue, y.IntValue);
}
public int GetHashCode(SampleValueObject obj)
{
_ = obj ?? throw new ArgumentNullException(nameof(obj));
return HashCode.Combine(typeof(SampleValueObject), obj.StringValue, obj.IntValue);
}
}

By using the EqualityComparerAssertion, we can test all the equality properties listed above in a single unit test.

[Test]
public void Equality_comparer_is_correctly_implemented()
{
//ARRANGE
var fixture = new Fixture();
var assertion = fixture.Create<EqualityComparerAssertion>();
// ACT & ASSERT
assertion.Verify(typeof(SampleValueObjectEqualityComparer));
}