Fluent Assertions Extensions
I started to get into Test Driven Development this past year seriously. I've always intended to try testing, and I have for the past 5 years, but I would try writing tests only after I've already know the code I have is working. On top of that, I couldn't get past the several dependencies every object had in the website I was working on for Smart Furniture, which was a fork of nopCommerce from roughly 2009. I recall one of the services having a constructor that took nearly 27 dependencies - try writing that manually.
As an aside, I added a dependency one day, and it was a new dependency in the middle of the constructor, not the end. I needed to update the test, but intellisense cut off before it got that far, so I had no idea which parameter to add/update in the test code without manually counting. If I made a programming horror post, it'd be about that memory.
In my travels to TDD world, I came across Fluent Assertations, and what a world of difference it has made. It's an amazing library and it helps me to express myself much better in my test code, especially when combined with several other great libraries out there. If you're not aware of Fluent Assertions, it let's you use a Fluent Interface (e.g. LINQ) to create test assertions for automated tests. For example, take this small code comparison:
public void AddTest()
{
var firstNumber = 4;
var secondNumber = 2;
var result = MyAdder.Add(4, 2);
//nunit
Assert.IsTrue(result == 6);
Assert.IsEqual(6, result);
Assert.That(result, Is.EqualTo(6));
//Fluent Assertion
result.Should().Be(6);
}
The shortest assertion, in length, is Fluent Assertion's. However, I also think it's the easiest to read and the most concise - it doesn't waste my time.
However, there are a few things I came across that I wish the core library had. Some people have created extension libraries to work on top of Fluent Assertion - for example, there's a library that helps write Fluent Assertions for ASP.NET Core. I have a small collection of growing extensions that I haven't found anywhere else, and I haven't come across a library for these, so I created one. This library isn't crazy large, but as I find more things, I'll add to this library.
My first extension is something that has been discussed quite a bit on Fluent Assertion's Github issue tracker, although it goes much farther than my short usage with this project. One of the trade-offs the library has is that the typical .Should() is not generic. There's only a few generic Should()'s and they all have a constraint on the type. This is fine, as there's many overloads, but this causes a problem when you want to method chain an expression. For example:
//doesn't work - .Which doesn't exist
//because Should() here loses the type information
ingredient.Should().NotBeNull()
.Which.Name.Should().Be("celery");
//to do that, we'd have to re-chain form the start:
ingredient.Should().NotBeNull();
ingredient.Name.Should().Be("celery");
This isn't a big deal, but there are some cases where it'd be nice to have the interface keep the type information for various objects that don't have a special "Should()" already defined. The issue tracker, however, talks about how the compiler will pick the non-constraint open generic Should() over any other Should(), which now introduces a similar problem - you lose all the helpers for, say, Collections, if you had an open-generic Should().
There are some ways around that. You can target extension methods on the generic wrapper to only show when it knows the type is a collection. We might get into that, but for now, let's do a simpler initiative. If we change the name of our `Should()` slightly, we can keep the above behavior intact, and add new behavior. It's a little clunky, but it'll be the best way to handle the issue discussion above with the least amount of work right now. So, here's are small helper:
public static class DefaultFluentAssertionsExtensions
{
public static GenericObjectAssertions<T> GenericShould<T>(this T actualValue)
=> new(actualValue);
}
public sealed class GenericObjectAssertions<T> : ObjectAssertions<T, GenericObjectAssertions<T>>
{
private readonly T _value;
internal GenericObjectAssertions(T value) : base(value) => _value = value;
public AndWhichConstraint<GenericObjectAssertions<T>, T> BeDefault(
String because = "",
params Object[] becauseArgs)
{
...
}
public AndWhichConstraint<GenericObjectAssertions<T>, T> NotBeDefault(
String because = "",
params Object[] becauseArgs)
{
...
}
}
Instead of calling Should(), we can call GenericShould(), and be able to keep the underlying type informatino with us. We also added `NotBeDefault` and `BeDefault`, which is basically the equivalent of `NotBeNull` or `BeNull`, but returns and `AndWhichConstraint`, which let's us use `Which` to get back to the original type. This let's use method chaining to do the above call we desired to do:
ingredient.GenericShould().NotBeNull()
.Which.Name.Should().Be("celery");
Fantastic. What if I want to chain Which's together though? Right now, we'd have to restart the method chain once we `Which` into a property. We'll talk about that part next time.
For those who want to check it out, the source code is available here and the nuget package is available here.