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.
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):
The snippet below will test that every parameter of the constructor is guarded against null values.
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.
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.
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.
The ConstructorInitializedMemberAssertion
can be used to verify that the properties Parameter
and Value
contain the same values passed to the constructor.
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 overrideObject.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 returnfalse
.
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 thatObject.Equals
is implemented so thatx.Equals(new object())
is alwaysfalse
,EqualsNullAssertion
verifies thatObject.Equals
is implemented so thatx.Equals(null))
is alwaysfalse
,EqualsSelfAssertion
verifies thatObject.Equals
is implemented so thatx.Equals(x))
is alwaystrue
,EqualsSuccessiveAssertion
verifies thatObject.Equals
is implemented so that callingx.Equals(y)
several times returns always the same value,GetHashCodeSuccessiveAssertion
verifies thatObject.GetHashCode
is implemented so that callingx.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
We can define our own EqualityAssertion
as follows
We can then create a simple unit test like the one below
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
By using the EqualityComparerAssertion
, we can test all the equality properties listed above in a single unit test.
Last updated