Breaking with the past (or…Fluent Assertions 2.0 is in beta)

After many months of development, in particular during in evenings after work and in the weekends, we’re proud to present the first (and hopefully the only) beta of Fluent Assertions 2.0. Together with my good friend and coworker Martin Opdam, and supported by noticeable contributors like Oren Novotny and Ian Obermiller, we managed to introduce a shipload of new features.

For instance, Oren worked on the foundations for supporting the new Windows 8 Metro-style apps, whereas Ian created the initial Windows Phone 7.5 version. Martin added support for .NET 4.5, added quite a few new variations and overloads to the existing assertions and also fixed a lot of the reported bugs. I myself introduced a completely new extensible API for comparing two objects graphs for equivalence. As the original founder of this project, I also took charge of safeguarding the overall quality and consistency.

And now that I mention it, after a week of debugging I almost decided to drop Windows Phone support altogether. I was suffering from two really persistent ReflectionTypeLoadExceptions and MissingMethodExceptions. Since I’ve started developing on the .NET platform in 2001, I’ve run into some pretty weird issues, but this was a whole new league for me (read the StackOverflow discussion to learn a bit more about this issue). Fortunately Geert van Horrik pointed me at a blog post he wrote a while back that explained Windows Phone’s lack of support for co- and contravariance. And indeed, that was exactly the problem I was facing all the time.

So in addition to support for the aforementioned .NET flavors and support for MBUnit and the Gallio Automation Platform, what else did we add?

ContainInOrder() got an overload that takes a params object[] argument rather than an IEnumerable in case you don't care about the reason.

Added ContainSingle() that asserts there is only a single element in the collection that matches the specified lambda.

Added an overload to Equal() that takes a lambda that is used for checking the equality of two collections without relying on the type’s Equals() method. Consider for instance two collections that contain some kind of domain entity persisted to a database and then reloaded. Since the actual object instance is different, if you want to make sure a particular property was properly persisted, you usually do something like this:

Fixed a bug that occurred when two collections are compared for equality but the collection contains nulls.

What's new for strings

When strings differ in length, it will report the expected and actual lengths as part of the error messages.

An ArgumentOutOfRangeException was thrown when asserting that a string started with a specific string and the first string was shorter than the expected string.

What's new for numbers

Added support for using Be() on nullable numeric types.

Added BeOneOf() to assert that a value matches one of the provided values.

Added support for (nullable) decimals

BePositive() and BeNegative() now also work for floats and doubles.

What's new for dates and times

Added NotBe(). Also added BeOneOf() to verify that the value matches one of the provided values.

Added BeCloseTo() to assert that a date/time is within a specified number of milliseconds from another date/time value. This can be particularly useful if your database truncates date/time values.

If a date/time value has non-zero milliseconds then that will be displayed in the error messages .

What's new for comparing object graphs I’ll dedicate a separate blog post on the new object.ShouldBeEquivalentTo(object) and collection.ShouldAllBeEquivalentTo(collection), but the existing API based on the object.ShouldHave() has benefited from the internal redesign as well.

You can now apply the property equality comparisons to entire collections of objects. It doesn't matter what kind of collection type you use, as long as it contains the same number of objects, and the object’s properties match.

The error message now includes the index of the mismatching object.

An exception was thrown when comparing the properties of an object and one of them causes a cyclic reference. You can now alter that behavior by passing in a value from the CyclicReferenceHandling enum into the IncludingNestedObjects() method.

Added support for references to an interface rather than concrete types. You can find an example of that at the corresponding feature request page.

Added support for comparing two anonymous types using SharedProperties().

Fixed a bug that caused an exception to be thrown for write-only properties. They are now ignored.

Fixed a stack overflow exception when formatting an object graph that contains a static property with a cyclic reference.

Fixed an exception that was thrown when formatting an object graph and one of the properties throws.

The name of a date/time property was not included in the error message when AllProperties() failed on that property

What's new for types

Added the static AllTypes class with a static method From(Assembly assembly) as a wrapper around the Types extension method on Assembly. This allows for a more fluent syntax like

Improved several of the error messages related to assertions on XDocument and XElement.

All overloads that took the reason and reasonargs parameters have been removed and replaced with optional parameters

Allowed ShouldRaisePropertyChangeFor(null) to assert that an object implemented INotifyPropertyChanged raised the changed event for all properties.

What’s new for extensibility

The list of IValueFormatter objects on the Formatter class can be altered to insert a custom formatter.

Introduced a mechanism to override the way Fluent Assertions formats objects in an error message by annotating a static method with the [ValueFormatter] attribute. If a class doesn’t override ToString(), the built-in DefaultValueFormatter will render an object graph of that object. But you can now override that using a construct like this:

We introduced a mechanism so that the error message of custom assertion extensions can specify the {context} tag that is used to inject the property path in object graph comparisons. For instance, in the date/time assertions, this is used to display date and time. But when this assertion is used as part of a recursive object graph comparison, it will display the property path instead.

Breaking changesSince we decided to bump up the version from 1.7.1 to 2.0 and we’ve already told you that we comply to Semantic Versioning, you can expect some breaking changes in this release.

In order to ensure that all extension methods are always available through the FluentAssertions namespace, we had to get rid of the FluentAssertions.Assertions namespace. Just use a global search-replace to remove all those usage statements.

Many of the assertion classes have moved into dedicated namespaces. You might have to fix any code that inherits from those classes.

Remove the obsolete Verify() methods from the Verification class

So what’s nextTo get started, get the NuGet package from inside Visual Studio (just make sure you include pre-release versions), or get the ZIP through the CodePlex download page. If you run into issues, I would prefer that you ping me directly on Twitter before you file an issue on CodePlex’s Issue Tracker. By the way, we use the #fluentassertions tag on Twitter. For questions, remarks or suggestions, you can use the Discussions page, StackOverflow, or you can contact me directly by email or Twitter.Off topic: Although we’re still on CodePlex, the source code of Fluent Assertions is now stored in a Git repository. That should make it a whole lot easier for contributors to fork the code, write a nice improvement, and send us a pull request.

Share on

Leave a Comment

You May Also Enjoy

It may be coincidence, but the two best tutorials I attended at Agile DevOps East both ran on the same day. The first one focused mostly on agile transformation, but briefly touched on the leadership topic. This one, let by Jennifer Bonine, took this further by focusing on being a better leader b...

For my annual conference trip, I decided to skip the always-great QCon conference for once and instead attend Agile DevOps East in Orlando, Florida. In addition to the typical conference schedule, I also registered for some of the half-day and full day tutorials. One of them, How to lead high-per...

Transient vs non-transient exceptions
If I have to name the single biggest flaw in adopting Event Sourcing, it must be our decision to rely on the synchronous dispatching pipeline of NEventStore. It is based on the idea that every event will be processed by all projectors in a synchronous manner....