Writing Human Readable Unit Tests in C# With FluentAssertions

Writing and maintaining unit tests is something I actively encourage all developers to keep on top of code quality and keeping bugs down. I’m also a believer that unit test suites should be self documenting whereby any developer should be able to dive into a code base and understand functionality based on the tests.

The main problem I find is that test can become cluttered with Asserts and test-specific code which becomes increasingly more difficult to read through. Recently I’ve come across FluentAssertions, a code library which helps transform tests using a syntax that reads naturally which has helped reduce the lines of code I’m writing while saving time reading tests.

FluentAssertions is a set of .NET extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style test. FluentAssertions has a huge range of extension methods that can you can leverage in your test suite, below are some examples of the extension methods I find myself using frequently.

Examples

Basic Assertions

            object player1 = null;
            object player2 = "Mario";

            player1.Should().BeNull();
            player1.Should().NotBe(player2);
            player2.Should().NotBeNull();

            player1 = player2;
            player2.ShouldBeEquivalentTo(player1);
            player1.Should().Be(player2);

Strings
   
           var title = "The Legend Of Zelda";

            //Example individual calls
            title.Should().StartWith("The");
            title.Should().EndWith("Zelda");
            title.Should().NotStartWith("Zelda");
            title.Should().NotEndWith("The");

            title.Should().Contain("Legend Of");
            title.Should().NotContain("Super Mario");            

            title.Should().NotBeEmpty();
            title.Should().NotBeNullOrWhiteSpace();
            title.Should().HaveLength("The Legend Of Zelda".Length);

            title.Should().Be("The Legend Of Zelda");

            // chaining asserts together
            title.Should().StartWith("The").And.Contain("Legend Of").And.EndWith("Zelda");     
Booleans
            bool hasCape = false;
            hasCape.Should().BeFalse();               

            hasCape = true;
            hasCape.Should().BeTrue();
Integers
            int hp = 4;
            hp.Should().BeGreaterOrEqualTo(3);            
            hp.Should().BeGreaterThan(2);
            hp.Should().BeLessOrEqualTo(5);
            hp.Should().BeLessThan(6);
            hp.Should().BePositive();
            hp.Should().Be(4);
            hp.Should().NotBe(10);
            hp.Should().BeInRange(1, 10);
Dates

Not only does FluentAssertions provide a range of useful asserts for DateTime objects but it also provides a unique helper syntax for instantiating new DateTime objects which is far more readable at a glance than using the new DateTime syntax in your tests.

            var myBirthday = 1.March(1989).At(12, 00);

            myBirthday.Should().BeAfter(1.February(1988));
            myBirthday.Should().BeBefore(2.March(1990));
            myBirthday.Should().BeOnOrAfter(1.March(1989));           

            myBirthday.Should().HaveDay(1);
            myBirthday.Should().HaveMonth(3);
            myBirthday.Should().HaveYear(1989);
            myBirthday.Should().HaveHour(12);
            myBirthday.Should().HaveMinute(0);
            myBirthday.Should().HaveSecond(0);

            myBirthday.Should().BeSameDateAs(1.March(1989));
Collections

One of FluentAssertions best assets is its huge range of extension methods for asserting against collections, here are just a few examples of the main extension methods I find myself using on a daily basis however there are many more.

            IEnumerable drivers = new[] { "Mario", "Luigi", "Peach", "Yoshi" };           

            // Single Asserts
            drivers.Should().Equal(new List { "Mario", "Luigi", "Peach", "Yoshi" });            
            drivers.Should().BeEquivalentTo(new Collection { "Peach", "Yoshi", "Mario", "Luigi" });
            drivers.Should().NotBeEquivalentTo(new List { "Mario", "Luigi", "Peach", "Yoshi", "Bowser" });            
            drivers.Should().HaveSameCount(new[] { "Wario", "Bowser", "Donkey Kong", "Toad" });                     
               
            drivers.Should().StartWith("Mario");
            drivers.Should().EndWith("Yoshi");            
            
            drivers.Should().ContainItemsAssignableTo();
            drivers.Should().ContainInOrder(new[] { "Mario", "Luigi", "Peach", "Yoshi" });

            drivers.Should().Contain("Yoshi");
            drivers.Should().NotContain("Wario");
            drivers.Should().NotContainNulls();
            drivers.Should().NotBeNullOrEmpty();

            var noDrivers = new string[0];
            noDrivers.Should().BeEmpty();
            noDrivers.Should().BeNullOrEmpty();            
                        
            drivers.Should().IntersectWith(new[] { "Mario", "Luigi", "Peach", "Yoshi", "Wario", "Bowser", "Donkey Kong", "Toad" });
            drivers.Should().NotIntersectWith(new List { "Waluigi", "Diddy Kong" });

            //chaining asserts together
            drivers.Should()
                 .NotBeEmpty()
                 .And.HaveCount(4)
                 .And.ContainInOrder(new[] { "Mario", "Luigi", "Peach", "Yoshi" })
                 .And.ContainItemsAssignableTo();
Exceptions

Exception handling is also very clean using FluentAssertions with added flexibility of being able to assert against cases when exceptions shouldn’t be thrown (if absolutely necessary)

            // Assert exception is thrown
            Action equip = () => character.Equip(null));

            equip.ShouldThrow()
             .And.Message.Contains("Can't Equip Null!");

            // Assert and check inner exception
            Action equip = () => character.Equip("Water Pistol");

            equip.ShouldThrow()
                 .WithInnerException()
                 .WithInnerMessage("Water Pistol is not a valid weapon");

            // Assert Exception is not thrown
            Action equip = () => character.equip("Master Sword"));
            equip.ShouldNotThrow();

            // Assert specific type of exception isn't thrown 
            Action equip = () => character.equip("Big Goran Sord"));
            equip.ShouldNotThrow();

These are just a few of the ways you can make use of FluentAssertions, you can find their full set of documentation here. I hope that you’re also able to incorporate FluentAssertions into your projects and that it makes you and your team more productive.