Monday, April 12, 2010

Fluent Assertions 1.2 has been released

It has been only 6 weeks since I first released Fluent Assertions to the public, followed by version 1.1 a week later. In the weeks thereafter, I received some nice ideas from the community which caused me to start working on the next version.

But it was not easy. I found that designing a small framework like this really requires you to carefully design the syntax you want to offer. For instance, I underestimated the limits of the current version of C# and had to make some tough decisions along the way. But I'm done now, so get version 1.2 from CodePlex. However, please read this post before you start introducing it in your project. Those tough decisions will cause some minor inconvenience.

New collection assertions

The collection assertion methods now include a BeNull() and NotBeNull() to ensure that a collection is initialized or not. I’ve also made sure that the Contains() method of a generic collection takes an object of the right type instead of System.Object (this breaks existing usages though). And if that is not enough, you can also pass in a lambda expression to specify that the collection should contain a specific instance. Something like this:

dtoCollection.Should().Contain(dto => dto.Id != null);

Similarly, you can also use a lambda when asserting the count of a collection:

collection.Should().HaveCount(c => c >= 3);

If the subject-under-test does not comply with the expression, it will include the actual expression of the lambda in the error message. As a matter of fact, this applies to all assertion methods that take a lambda expression.

Comparing objects by their properties

I’ve added the possibility to assert the equality of entire objects by comparing their properties by name. This even works if the types of the properties differ but a built-in conversion exists (through the Convert class). As an example, consider a Customer entity from some arbitrary domain model and its DTO counterpart CustomerDto. You can assert that the DTO has the same values as the entity using this syntax:

dto.ShouldHave().AllProperties().EqualTo(customer);

As long as all properties of dto are also available on customer, and their value is equal or convertible, the assertion succeeds. You can, however, exclude a specific property using a lambda, such as for instance the ID property:

dto.ShouldHave().AllPropertiesBut(d => d.Id).EqualTo(customer);

The other way around is also possible. So if you only want to include two specific properties, use this syntax.

dto.ShouldHave().Properties(d => d.Name, d => d.Address).EqualTo(customer);

Comparing dates and times

In addition to simple assertions between two DateTime objects, I’ve added a whole bunch of methods for asserting that two dates or times differ a specific amount of time (specified using a TimeSpan). With these you can do things like:

dt1.Should().BeWithin(TimeSpan.FromHours(50)).Before(dt2);
dt2.Should().BeLessThan(TimeSpan.FromDays(1).After(dt2);

It supports BeMoreThan(), BeAtLeast(), BeExactly(), BeWithin() and BeLessThan() before or after a specific date and/or time.

Other improvements

In addition to fixing several minor bugs reported by the community, I have spend a considerable amount of time in making sure the assertion failure messages clearly explain why it failed. For instance, a date/time assertion might return the following (lengthy) exception message:

”Expected date and/or time <2010-04-08 09:59:59> to be within 2d and 2h before <2010-04-10 12:00:00> because 50 hours is enough, but it differs 2d, 2h and 1s.”.

The part after the because is the part that you need to add using one of the many overloads. It really helps to keep the developer from the need to start the debugger.

One particular bug I fixed was when asserting that a collection of strings contained a particular string, it was interpreted as an IEnumerable of Char. Obviously it never succeeded, regardless of the fact that the original string was part of the collection.

I also looked at extensibility a bit more by making classes public and ensuring you can override an assertion class at the right spots. I still have to do some work here, but whenever somebody has a problem, I’ll fix it soon enough.

So what did you break?

As a result of some important feedback from the community, my desire to remove a lot of noise from the syntax, and limitations of the language I ran into, I decided to introduce some changes that will break existing code. Fortunately, it is just a cosmetic change, so some smart search/replace should get you back on track easily.

First of all, I’ve renamed almost all Equal() methods to Be(). So instead of myString.Should().Equal(“hello”) you now have to write myString.Should().Be(“hello”). The only exception to this is the one for comparing two collections for equality, since it’s purpose to see if they have the same items.

Also, I’ve renamed the Satisfy() method for asserting an arbitrary object against a lambda expression to Match(). Apparently, the word satisfy was a bit to technical. On the flipside, you now get multiple overloads which allows you to do the following.

o.Should().Match(obj => obj == null)
o.Should().Match<CustomerDto>(d => d.Name.Length > 0);
o.Should().Match((CustomerDto d) => d.Name.Length > 0);

The whole exception throwing assertion syntax was way too noisy, so I removed some unnecessary parts and changed the overall syntax. For instance

someObject.ShouldThrow(x => x.SomeMethod()).Exception<SomeException>();

has been changed into

someObject.Invoking(x => x.SomeMethod()).ShouldThrow<SomeException>();

Which doesn’t really remove noise, but feels more natural to me when invoking a lambda expression. When using an Action<T>, the syntax has changed as well. Consider a class with a method Foo() that should throw an instance of SomeException:

Action act = () => someObject.Foo();

In 1.1 asserting that Foo() really threw that specific exception with specific values for its Message and a custom property SomeParam had to be done like this:

act.ShouldThrow().Exception<SomeException>().
   And.WithMessage(“Something went wrong”).
   And.ValueOf.SomeParam.Should().Equal(23);

In 1.2 I’ve changed it into:

act.ShouldThrow<SomeException>().
   WithMessage(“Something went wrong”).
   And.SomeParams.Should().Equal(23);

You can download version 1.2 from here.