[Root] / Extensibility / Best Practices Guide
In This Topic
    Best Practices Guide
    In This Topic

    This article focuses on providing a step by step guide on best practices in class design and structure when writing custom Fluent Assertions. Once this is well understood, have a look at more advanced features and examples in the general extensibility article. The tips article also has useful suggestions on the kind of assertions to write and how to name them in order to get the most useful output.

    Why write custom assertions?

    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.

    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 is not necessary. The Fluent Assertions 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

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

    will work as expected and display the output

    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

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

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

    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:

    Copy Code
    MyClass myClassInstance2 = new MyClass();
    
    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.

    Derive from an existing Fluent Assertions class

    Copy Code
    public class MyClassAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions<MyClass, MyClassAssertions>
    {

    It is very often a good idea to derive a custom assertion class from an existing Fluent Assertions type that matches the kind of class being asserted. It will often be as simple as deriving from the ReferenceTypeAssertions class, which contains useful basic assertions for reference types (classes as opposed to structures) like BeNull(), BeSameAs() and Match().

    In this example, it makes sense that MyClassAssertions derive from FluentAssertions.Primitives.ReferenceTypeAssertions<TSubject, TAssertions>.

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

    Write a constructor

    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 MyClassAssertions class constructor.

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

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

    Override the Identifier property

    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 ReferenceTypeAssertions base class so it must be overridden here. It needs to know what name to use in the failure message in case the subject identification functionality can't figure out the variable that was used to call Should() on . 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 by telling Fluent Assertions to ignore any Should() calls made in custom assertion code while performing subject identification.

    Alternatively, you can add the [assembly:CustomAssertionsAssembly] attribute to a file within the project to tell Fluent Assertions that all code in that assembly should be treated as custom assertion code.

    BeInAValidState()

    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 supplied explanation of why the assertion is expected 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:MyClass} 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);
    }
    
    private static bool IsStateValid(MyClass myClassInstance)
    {
        bool isStateValid;
    
        /* Use various property values to determine if the state is valid or not */
        isStateValid = !String.IsNullOrWhiteSpace(myClassInstance.Property1) && myClassInstance.Property2 > 0;
    
        return isStateValid;
    }

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

    The method performs the essential tasks of an assertion method.

    1. Adds the supplied explanation of why the assertion is expected to succeed using BecauseOf(). This will provide a value for the {reason} substitution identifier in the FailWith() message parameter.
    2. Tests the condition that defines the assertion; in this case, the test logic is put in a separate method called IsStateValid().
    3. Sets the result of the test in the assertion chain using ForCondition().
    4. Have the chain throw the appropriate exceptions if the test failed using FailWith(). The method supports special identifiers for substitution in the message parameter value:
    5. Returns a logical constraint object that allows another assertion to be performed with the same chain.

    Advanced examples demonstrating more features of Fluent Assertions can be found in the main Extensibility article.

    NotBeInAValidState()

    Copy Code
    [CustomAssertion]
    public AndConstraint<MyClassAssertions> NotBeInAValidState(string because = "", params object[] becauseArgs)
    {
        this.CurrentAssertionChain
            .BecauseOf(because, becauseArgs)
            .ForCondition(!MyClassAssertions.IsStateValid(this.Subject))
            .FailWith("Did not expect {context:MyClass} to 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. This is the way assertion methods are usually written. It works because many of the methods of the AssertionChain class return this, making it possible to chain several calls in succession.

    Extension method

    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 the object on which asserts will be performed. This is the instance parameter.

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

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

    Finally, the initialized assertions object is returned to the caller.

    Custom assertions for class hierarchies

    Consider the following simple class hierarchy.

    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 define assertion classes to get the benefits of inheritance and polymorphism.

    What should be asserted

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

    With class hierarchies, concentrating on custom assertions for concepts that are not explicitly part of the class public API is a good strategy just like simple classes.

    Which classes should get assertions

    Not every class in a hierarchy needs to have a dedicated assertions class. The base class at the root of the tree should at least have an assertions class so it contains assertions and utility methods common to all derived classes.

    If there is nothing to assert in a derived class, no need to define an assertions class for it. That class will still benefit from the assertions in the base.

    Assertions for 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 these examples, is to reproduce the class hierarchy as assertion classes.

    Generic assertions class

    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 defined.

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

    Concrete assertions class variant

    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 almost all the time in code.

    Write a constructor

    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. Notice the use of the TSubject type parameter where appropriate.

    Override the Identifier property

    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 are marked with the [CustomAssertion] attribute.

    BeInAValidState()

    Copy Code
    [CustomAssertion]
    public AndConstraint<TAssertions> BeInAValidState(string because = "", params object[] becauseArgs)
    {
        this.CurrentAssertionChain
            .BecauseOf(because, becauseArgs)
            .ForCondition(this.IsStateValid())
            .FailWith("Expected {context:{0}} to be in a valid state{reason}, but {1} is not.", this.Identifier, this.Subject);
    
        // Create an 'AndConstraint' object that will allow to chain our assertions object with another assertion through the 'And' property
        return new AndConstraint<TAssertions>( ( TAssertions ) this);
    }

    The method performs the essential tasks of an assertion method.

    Notice part of the failure message: {context:{0}} . In the optional default value, instead of hard-coding the name of the class, a numbered identifier representing the value of the Identifier property is used instead. This ensures that, in derived classes, the actual type name will be used in the message because the Identifier property is virtual.

    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;
    }

    The method being virtual makes it so that BeInAValidState(), and other methods that call it, will evaluate the state in the actual derived assertion class, if applicable.

    NotBeInAValidState()

    Copy Code
    [CustomAssertion]
    public AndConstraint<TAssertions> NotBeInAValidState(string because = "", params object[] becauseArgs)
    {
        this.CurrentAssertionChain
            .BecauseOf(because, becauseArgs)
            .ForCondition(!this.IsStateValid())
            .FailWith("Did not expect {context:{0}} to be in a valid state{reason}, but {1} is.", this.Identifier, this.Subject);
    
        // Create an 'AndConstraint' object that will allow to chain our assertions object with another assertion through the 'And' property
        return new AndConstraint<TAssertions>((TAssertions)this);
    }

    This is the same as BeInAValidState() except the result of IsStateValid() is negated.

    Assertions for derived class

    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 defined.

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

    A concrete assertions class variant is also defined to make it easy to create an instance in everyday use.

    Write a constructor

    Copy Code
    public MyDerivedClassAssertions(TSubject instance, AssertionChain assertionChain)
        :base(instance, assertionChain)
    {
    
    }

    The base class constructor must be called so its signature is reproduced in the derived class constructor. Notice the use of the TSubject type parameter where appropriate.

    Override the Identifier property

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

    Simply returns the type name of the class being asserted.

    Write assertion methods

    Copy Code
    protected override bool IsStateValid()
    {
        /* Use base class and various property values to determine if the state is valid or not */
        bool isStateValid = base.IsStateValid() && this.Subject.Size > 0;
    
        return isStateValid;
    }

    The class does not need to override the assertion methods BeInAValidState() and NotBeInAValidState(), only the IsStateValid() utility method. Through polymorphism, the base class implementation will call this derived flavor when a MyDerivedClass object is asserted.

    Extension method

    Copy Code
    public static class MyClassHierarchyExtensions
    {
        public static MyBaseClassAssertions Should(this MyBaseClass instance)
        {
            return new MyBaseClassAssertions(instance, AssertionChain.GetOrCreate());
        }
    
        public static MyDerivedClassAssertions Should(this MyDerivedClass instance)
        {
            return new MyDerivedClassAssertions(instance, AssertionChain.GetOrCreate());
        }
    }

    Extension methods named Should() on MyBaseClass and MyDerivedClass tie everything together. They receive an instance of the object on which asserts will be performed. The approach is the same as explained above.

    With a class hierarchy, each assertion class gets its own Should() method.

    See Also