Why I created Fluent Assertions in the first place
The intention of a unit test must be clear from the code
An intention revealing unit test has the following characteristics
- The arrange, act and assertion parts are clearly distinguishable
- The name represents the functional scenario the test verifies
- The assertion provides sufficient context to understand why a specific value is expected
public void When_assigning_the_endorsing_authorities_it_should_update_the_property()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
Permit permit = new PermitBuilder().Build();
//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
permit.AssignEndorsingAuthorities(new[] { "john", "jane"});
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
permit.EndorsingAuthorities.Should().BeEquivalentTo(new[] { "john", "jane"});
}
public void When_a_substance_is_specified_that_is_not_required_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var permit = new PermitBuilder().Build();
var someUser = new UserBuilder().Build();
var dataMapper = new InMemoryDataMapper(permit, someUser);
var service = new CommandServiceBuilder().Using(dataMapper).Build();
//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
try
{
service.Execute(new AddMeasurementsCommand
{
Id = permit.Id,
Version = permit.Version,
Username = someUser.Username,
Measurements = new[]
{
new MeasurementData("Oxygen", 1.1d, new DateTime(2011, 4, 13, 16, 30, 0))
},
});
Assert.Fail("The expected exception was not thrown");
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
catch (InvalidOperationException exc)
{
Assert.IsTrue(exc.Message.Contains("not required"));
}
}
// Act
//-----------------------------------------------------------------------------------------------------------
Action action = () => service.Execute(new AddMeasurementsCommand
{
Id = permit.Id,
Version = permit.Version,
Username = user.Username,
Measurements = new[]
{
new MeasurementData("Oxygen", 1.1d, 13.April(2011).At(16, 30)),
},
});
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
action
.ShouldThrow<InvalidOperationException>()
.WithMessage("not required", ComparisonMode.Substring);
Something similar is also possible for verifying that events have been raised, with special support for the INotifyPropertyChanged interface so common in Silverlight and WPF projects. I blogged about that earlier this year.
Nothing is more annoying then a unit test that fails without clearly explaining why. More than often, you need to set a breakpoint and start up the debugger to be able to figure out what went wrong. Jeremy D. Miller once gave the advice to "keep out of the debugger hell" and I can only agree with that.
For instance, only test a single condition per test case. If you don't, and the first condition fails, the test engine will not even try to test the other conditions. But if any of the others fail, you'll be on your own to figure out which one. I often run into this problem when developers try to combine multiple related tests that test a member using different parameters into one test case. If you really need to do that, consider using a parameterized test that is being called by several clearly named test cases.
Obviously I designed Fluent Assertions to help you in this area. Not only by using clearly named assertion methods, but also by making sure the failure message provides as much information as possible. Consider this example:
This will be reported as:
The fact that both strings are displayed on a separate line is on purpose and happens if any of them is longer than 8 characters. However, if that's not enough, all assertion methods take an optional formatted reason with placeholders, similarly to String.Format, that you can use to enrich the failure message. For instance, the assertion
The code itself should be a great example of what high-quality code should look like
This goal should be rather obvious. I don't only want to deliver a great framework, I also want people to learn from it. And isn't that the single biggest reason why people join open-source projects? However, the article I mentioned before states that the vision behind an open-source project should be much more important than the quality of the source code. As you might expect, I don't agree with that. In fact, one of the challenges I ran into was my desire to control all code contributions so that they complied with Clean Code, my own coding guidelines and my ideas about unit testing.
At first the article made me doubt about the approach to take, but then I decided that the quality of the code was just as important. I now respond to all contribution requests with some information on the way I'd like FA to see evolve. Additionally, I've set-up a dedicated contribution branch that they can use. Depending on the quality of their contribution, merging them into the main branch requires a corresponding amount of work at my side. I know that this might keep some contributors away, but up to now, most of of them agreed with my approach and were willing to deliver high-quality code.
Leave a Comment