Fluent Assertions Documentation
Extensibility / Extensibility Guide
In This Topic
    Extensibility Guide
    In This Topic

    Custom assertions for applications

    Why write custom assertions for an application?

    Most large or well-structured applications contain classes and structures that encapsulate the application's data and its related operations. It can be useful to have custom assertions directly related to these classes. These assertions can then be used in many different unit tests and help avoid duplicating certain test-related code or logic that could otherwise be duplicated in each unit test.

    Custom assertions for simple classes

    Consider the following class.

    C#
    Copy Code
    public class MyClass
    {
        public string Property1 { get; set; }
        public int Property2 { get; set; }
    
        public bool Method1(string s)
        {
            return false;
        }
    }

    What should be asserted

    The class has two public properties and a method.

    Writing custom assertions for simple properties and method return values are not necessary. The FluentAssertions automatic subject identification provides enough context to generate a useful output message in most cases.

    A good approach is to write custom assertions for concepts that are not explicitly part of the class public API.

    Properties are handled automatically

    The assertion chain

    C#
    Copy Code
    MyClass myClassInstance1 = new MyClass();
    MyClass myClassInstance2 = new MyClass();
    
    myClassInstance1.Property1 = "012345aa";
    //myClassInstance1.Property1.Should().NotBe("SomeIncorrectValue").And.BeLowerCased().And.NotEndWith("aa");

    will work as expected and display the output

    Copy Code
    Expected myClassInstance1.Property1 not to end with "aa", but found "012345aa".

    Notice how the variable name myClassInstance1 from the code is picked up automatically and included in the failed assertion output.

    Method return values are handled automatically

    The same goes for the method return value

    C#
    Copy Code
    myClassInstance1.Method1("Some string").Should().BeTrue();

    The Should() method applies to the bool return value of the method and FluentAssertions automatically picks up the method call text from the code and includes it in the output:

    Copy Code
    Expected myClassInstance1.Method1("Some string") to be True, but found False.

    Custom assertions

    In the context of the simple class described above, a good candidate for a custom assertion could be one that asserts something that is not in the class' public API. For example:

    C#
    Copy Code
    myClassInstance1.Should().BeInAValidState();
    myClassInstance2.Should().NotBeInAValidState();

    The following sections will describe how to implement these assertions. This happens in the following steps:

    1. Write an assertions class. It will contain all the assertion methods related to MyClass like the ones in the example above.
    2. Write a Should() extension method to MyClass that returns an instance of the assertion class.

    The assertions class

    Here is an implementation

    C#
    Copy Code
    public class MyClassAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions<MyClass, MyClassAssertions>
    {
        #region Constructors 
        public MyClassAssertions(MyClass instance, FluentAssertions.Execution.AssertionChain assertionChain)
            :base( instance, assertionChain )
        {
    
        }

    Derive from an existing FluentAssertions class

    It is very often a good idea to derive a custom assertion class from an existing FluentAssertions class that more or less matches the type of the class being asserted. It will often be as simple as deriving from the ReferenceTypeAssertions<TSubject,TAssertions> class, which contains useful basic assertions for reference types (objects as opposed to structures) like BeNull, BeSameAs and Match.

    In this example, MyClassAssertions derives from the typical ReferenceTypeAssertions<TSubject,TAssertions>.

    Assertion classes are often generic classes that typically take the following type parameters

    At least one constructor is needed

    C#
    Copy Code
    public MyClassAssertions(MyClass instance, FluentAssertions.Execution.AssertionChain assertionChain)
        :base( instance, assertionChain )
    {
    
    }

    The base class constructor must be called so its signature is reproduced in the derived class constructor.

    The MyClass instance parameter value will be stored and used by each assertion method to test the object.

    The AssertionChain assertionChain parameter value will be used to pass context information from one assertion to another. It is one of the core constructs that allow FluentAssertions to be...fluent and provide excellent output messages.

    Override the Identifier property

    C#
    Copy Code
    protected override string Identifier
    {
        // Return a user-friendly string of the type the assertions in this class apply to
        get { return "MyClass"; }
    }

    The property is abstract in the base class so an implementation is required here. Simply returning the type name of the class being asserted is often good enough here.

    Write assertion methods

    Custom assertion methods must be marked with the [CustomAssertion] attribute. It enables the subject identification functionality to work correctly, allowing Fluent Assertions to render more meaningful test fail messages.

    BeInAValidState()
    C#
    Copy Code
    [CustomAssertion]
    public AndConstraint<MyClassAssertions> BeInAValidState(string because = "", params object[] becauseArgs)
    {
        // Get the subject of the assertion
        MyClass myClassInstance = this.Subject;
    
        // Get the current assertion chain
        AssertionChain chain = this.CurrentAssertionChain;
    
        // Add the explanation of why the assertion is supposed to succeed
        chain = chain.BecauseOf(because, becauseArgs);
    
        // Determine if the subject is in a valid state
        bool isInAValidState = MyClassAssertions.IsStateValid(myClassInstance);
    
        // Set the result of the test in the assertion chain
        chain = chain.ForCondition(isInAValidState);
    
        // Evaluate the condition and throw the appropriate exceptions
        chain.FailWith("Expected {context} to be in a valid state{reason}, but {0} is not.", myClassInstance);
    
        // Create an 'AndConstraint' object that will allow to chain our assertions object with another assertion through the 'And' property
        return new AndConstraint<MyClassAssertions>(this);
    }

    Every step is coded and explained in detail in this example for educational purposes. It does not have to be as explicit, as the next example will show.

    NotBeInAValidState()
    C#
    Copy Code
    [CustomAssertion]
    public AndConstraint<MyClassAssertions> NotBeInAValidState(string because = "", params object[] becauseArgs)
    {
        this.CurrentAssertionChain
            .BecauseOf(because, becauseArgs)
            .ForCondition(!MyClassAssertions.IsStateValid(this.Subject))
            .FailWith("Expected {context} to NOT be in a valid state{reason}, but {0} is.", this.Subject);
    
        return new AndConstraint<MyClassAssertions>(this);
    }

    This example performs the same tasks as the previous one (except that the condition is negated) but in a more compact way. It works because many of the methods of the AssertionChain class return this, making it possible to chain several calls in succession.

    Extension method

    C#
    Copy Code
    public static class MyClassExtensions
    {
        public static MyClassAssertions Should(this MyClass instance)
        {
            // Create an assertion chain or get an existing one that has been marked for reuse
            AssertionChain chain = FluentAssertions.Execution.AssertionChain.GetOrCreate();
    
            // Wrap the assertion chain around a new assertions class
            return new MyClassAssertions(instance, chain);
        }
    }

    An extension method named Should() on MyClass ties everything together. It receives an instance of the object on which asserts will be performed.

    First, AssertionChain.GetOrCreate() creates an assertion chain or gets an existing one that has been marked for reuse, like when Which is used.

    Then, a new instance of the assertions class that we wrote earlier is returned and is given the object and the assertion chain.

    Custom assertions for class hierarchies

    Consider the following simple class hierarchy.

    C#
    Copy Code
    public class MyBaseClass
    {
        public string Name { get; set; }
    
        public int Operation1(int value)
        {
            return 5;
        }
    }
    
    public class MyDerivedClass : MyBaseClass
    {
        public long Size { get; set; }
    
        public string[] Operation2(object data)
        {
            return new string[]
                {
                    "Some string",
                    "Another string",
                };
        }
    }

    The focus of this example will be on how to declare the assertion classes to get the benefits of inheritance and maintain the correct object types.

    What should be asserted

    As with the simple class example above, writing custom assertions for simple properties and method return values are not necessary. Properties are handled automatically. Method return values are handled automatically.

    It is best to concentrate on writing custom assertions for concepts that are not explicitly part of the class public API.

    Assertions base class

    It is possible to write a single assertions class that holds all the assertion methods for the entire class hierarchy. Another approach, and the focus of this example, is to reproduce the class hierarchy as assertion classes.

    Generic assertions class

    C#
    Copy Code
    public class MyBaseClassAssertions<TSubject, TAssertions> : ReferenceTypeAssertions<TSubject, TAssertions>
        where TSubject : MyBaseClass
        where TAssertions : MyBaseClassAssertions<TSubject, TAssertions>
    {

    Here an assertions class MyBaseClassAssertions using generic type parameters is declared.

    Again, the typical ReferenceTypeAssertions is used as a base class. The TSubject and TAssertions type parameters are used to define the base class

    Concrete assertions class variant

    C#
    Copy Code
    public class MyBaseClassAssertions : MyBaseClassAssertions<MyBaseClass, MyBaseClassAssertions>
    {
        public MyBaseClassAssertions(MyBaseClass instance, AssertionChain assertionChain)
            : base(instance, assertionChain)
        {
    
        }
    }

    Creating the assertion class using the type parameters is almost always reserved for when a derived class is defined. The rest of the time, the goal will be to create an instance of MyBaseClassAssertions<MyBaseClass, MyBaseClassAssertions>.

    Having a class simply named MyBaseClassAssertions is therefore a useful shortcut. That is the class name that will be used most often in code.

    At least one constructor is needed

    C#
    Copy Code
    public MyBaseClassAssertions(TSubject instance, AssertionChain assertionChain)
        :base( instance, assertionChain )
    {
    
    }

    The base class constructor must be called so its signature is reproduced in the derived class constructor.

    Override the Identifier property

    C#
    Copy Code
    protected override string Identifier
    {
        // Return a user-friendly string of the type the assertions in this class apply to
        get { return "MyBaseClass"; }
    }

    The property is abstract in the base class so an implementation is required here. Simply returning the type name of the class being asserted is often good enough here.

    Write assertion methods

    Custom assertion methods must be marked with the [CustomAssertion] attribute. It enables the subject identification functionality to work correctly, allowing Fluent Assertions to render more meaningful test fail messages.

    BeInAValidState()

    C#
    Copy Code
    protected override string Identifier
    {
        // Return a user-friendly string of the type the assertions in this class apply to
        get { return "MyBaseClass"; }
    }
    C#
    Copy Code
    protected virtual bool IsStateValid()
    {
        bool isStateValid;
    
        /* Use various property values to determine if the state is valid or not */
        isStateValid = !String.IsNullOrWhiteSpace(this.Subject.Name);
    
        return isStateValid;
    }

    Making the utility method IsStateValid() virtual makes it available to be used by derives assertion classes.

    Assertions derived class

    C#
    Copy Code
    public class MyDerivedClassAssertions : MyDerivedClassAssertions<MyDerivedClass, MyDerivedClassAssertions>
    {
        public MyDerivedClassAssertions(MyDerivedClass instance, AssertionChain assertionChain)
            : base(instance, assertionChain)
        {
    
        }
    }
    
    public class MyDerivedClassAssertions<TSubject, TAssertions> : MyBaseClassAssertions<TSubject, TAssertions>
        where TSubject : MyDerivedClass
        where TAssertions : MyDerivedClassAssertions<TSubject, TAssertions>
    {

    Here an assertions class MyDerivedClassAssertions that derives from MyBaseClassAssertions using generic type parameters is declared.

    The base class is MyBaseClassAssertions<TSubject, TAssertions>. This works because the values used for the type parameters are constrained to types that MyBaseClassAssertions accepts.