In the last couple of months, me and colleague Martin Opdam have spent a considerable amount of time on both improving the reporting capabilities of Fluent Assertions as well as fixing and incorporating various community contributions. Because of the many changes required, a very busy client project, and my endeavors around moving to a new house, it took us over four months to complete the next version. So let's quickly highlight the changes.
So what's new?
A contributor working under the CodePlex account MatFiz added the missing counterpart of string.Should().Contain(), consistently named string.Should().NotContain(). And while he was doing that, he also included the case-insensitive versions string.Should().ContainEquivalentOf() and string.Should().NotContainEquivalentOf(). He also included fluent equivalents of string.IsNullOrWhiteSpace() through string.Should().BeBlank() and string.Should().NotBeBlank().
Asserting that a string matches a particular wildcard pattern can now be done using string.Should().Match() and the case-insensitive MatchEquivalentOf(), both supporting the familiar * and ? Characters. You might wonder why we choose for ordinary wildcards rather than Regular Expressions, and the answer has everything to do with keeping your tests intention revealing. I haven't met a lot of developers that know all the regex escape codes by heart. But I do know a few that are capable of writing with some very obscure (but technically correct) expressions. And that isn't going to help the majority of the developers.
Another request involved asserting that an object can be serialized and then deserialized using the binary or XML formatters. I never felt the need for something like that, but I decided to include the object.Should().BeBinarySerializable() and object.Should().BeXmlSerializable() anyhow. Both of them will serialize an object to a MemoryStream using the corresponding serializer and then use FA's property comparison assertions to compare the properties of the deserialized object with the original object. Notice that this works only for objects that support full round-trip serialization.
While Martin introduced the possibility to execute various assertions on IDictionary<T>, including checking for specific keys, values or a combination of both, at roughly the same time I added support for IComparable<T> through methods such as comparable.Should().BeLessThan(), BeGreaterOrEqual(), BeNull() or BeInRange().
One of the things I've always have been annoyed with is the fact that when you assert that an exception was thrown with a particular exception message, you had to specify the entire message, including punctuation and whitespace. But usually I don't really care about the specifics, and only need to ensure myself that parts of the message match. But since we've introduced wildcard-based string matching in this release anyway, it didn't require a lot of work to support this:
action.ShouldThrow<ArgumentOutOfRangeException>().WithMessage( "code InvalidCode does not match a known", ComparisonMode.Substring);Or this:
action.ShouldThrow<AssertFailedException>().WithMessage( "Expected object*World*to be less than*City*because a city is smaller than the world.", ComparisonMode.Wildcard);
Another little neat new feature is a fluent API for specifying dates and times. For example, consider this.
var period = new Period( new DateTime(2011, 2, 1, 8, 0, 0), new DateTime(2011, 2, 1, 18, 00));
you're into fluent interfaces like me (if not, why would you be here
anyway :-)), this is a much better read, don't you think?
var period = new Period(1.February(2011).At(08, 00), 1.February(2011).At(18, 00));
Because I don't like spending time in my debugger, the majority of this release was spent on the reporting side. I've tried to make sure that FA reports failures as clear and intention revealing as possible.
For instance, object.ShouldHave().AllProperties().EqualTo(object) now internally uses the equality assertions appropriate for the type of property. This significantly improves the details reported for a string or IEnumerable<T> property.
The property assertion also throws with a clearer explanation when the types of equally named properties are not convertible.
Where's the difference?
Another area that has been improved is the way string differences are reported. This applies both to exception message assertions as well as direct string comparisons. The exception assertions will always display the expected and the actual messages on separate lines so that it is easier to spot the difference.
String assertions will do the same, but only if one of the involved strings contains a newline or is longer than 6 characters. As you've probably already noticed, line breaks and other special characters are escaped now.
An object is not an object
Something I already benefited from during dogfooding an intermediate release in a client project is that FA will now display the public properties of the involved objects whenever an assertion failure occurs.
Before this change, the only thing you got was a message along the line of "Expected object classname to be equal to classname, but they weren't". This will surely help tracking down the problem in your production code. And notice, FA will only do this if the object involved doesn't override ToString(). And since the collection assertions now use the same formatting infrastructure as the rest of FA, collections of objects will also be displayed using the actual structure of the object. In fact, it even support nested collections.
Did I break anything?
Upon specific request from a current user, I did change the behavior of the collection.Be(Not)SubsetOf() method so that an empty set is now treated as a subset of any set. Something similar happened to the collection.Be(Not)EquivalentTo() method; an empty set is now treated as equivalent to another empty set. So if you find that some of your tests start to fail after the upgrade, make sure they don't rely on this behavior.
Also, if you have been extending FA using the Execute class, pay particular attention to the release notes. We've changed the way you need to refer to the because of an assertion and marked the old API as obsolete.
How do I get started?