BDD, Unit Tests and the Power of Fluent Assertions
Join the DZone community and get the full member experience.
Join For FreeAn essential part of a well-written unit test is a well-written assertion. The assertion states the behavior you expect out of the system. It should tell you at a glance what a test is trying to demonstrate. It should be simple and obvious. You should not have to decipher conditional logic or sift through for loops to understand what it is doing. In addition, any non-trivial logic in a test case increases the risk of the test itself being wrong.
Over recent years there has been a rise in the popularity of tools and techniques that make it easier to write more fluent code, both for production code and for tests. In the testing space in particular, there are many libraries that now support fluent assertions in different languages. Fluent assertions are simply ways of writing assertions in a more natural, more readable and more expressive manner.
There are two main flavors to fluent assertions. The first typically uses the word “assert”, whereas the second uses terms like “should” or “expect”. The first approach comes from a more traditional unit testing background, and focuses on testing and verification. Indeed, you typically make an assertion about something that has already happened, or a result that has already been calculated. The second is more BDD-centric: the words “should” and “expect” describe what you think the application should do, regardless of what it does currently, or if it even exists.
Let's look at a few.
Fluent assertions in JavaScript
JavaScript has a number of libraries that can help make your assertions more expressive. Jasmine has the built-in expect() function, and Should.js and Chai also support similar features. Chai is probably the most flexible of these, supporting both the expect and should formats, as well as the older-style "assert". Chai focuses on using method chaining to make the assertions fluid and readable. For example, imagine we are building a Frequent Flyer web site for a large airline with a Javascript client. We need to check that the Frequent Flyer status is Bronze. Using Chai, we could write:
var expect = require('chai').expect
...
expect(frequentFlyer.getStatus()).to.equal('Bronze');
As you might expect, Chai supports a rich collection of assertions, and can chain multiple assertions together. For example:
var obtainableStatuses = ['Silver','Gold','Platinum']
...
expect(obtainableStatuses).to.have.length(3).and.to.include('Gold')
Chai also supports the more BDD-style should assertion, as illustrated here:
var expect = require('chai').should();
frequentFlyer.getStatus().should.equal('Bronze');
obtainableStatuses.should.have.length(3).and.include('Silver');
Both styles are equally expressive, so the choice is largely a question of style and personal preference.
Fluent assertions in Java and .NET
Fluent assertion libraries also exist for static languages such as Java and .Net, although they are generally a bit less expressive than their dynamic equivalents. Java, for example has several fluent assertion libraries. The two most well known are Hamcrest and FestAssert . And more recent versions of NUnit come with a similar constraint-based assert model.
All of these tools move away from the older style Assert methods, and let you express your expectations in a more fluent and concise manner. In particular, these libraries propose a number of higher-level assertions on collections, and can be extended to work with domain objects, which avoids having to put too much logic within your unit tests. Imagine we want to say that a Frequent Flyer should initially have a status of Bronze. In traditional JUnit, we would write something like this:
assertEquals(BRONZE, member.getStatus());
The parameter order and the somewhat clumsy wording make this sort of assertion less than ideal. It does not read fluently or naturally, which limits our ability to express our expectations easily and quickly. An equivalent Hamcrest assertion, on the other hand, would look like this:
FrequentFlyer member = FrequentFlyer.withFrequentFlyerNumber("12345678")
.named("Joe", "Bloggs");
assertThat(member.getStatus(), is(FrequentFlyerStatus.BRONZE));
FestAssert does something similar, but using a different syntactic structure:
assertThat(member.getStatus()).isEqualTo(FrequentFlyerStatus.BRONZE);
And in NUnit, we could write something like this:
Assert.That(member.getStatus(), Is.EqualTo(FrequentFlyerStatus.BRONZE));
All of these libraries propose a rich set of matchers, including a number of convenient operations on lists. For example, to check that the list of unachieved statuses contains both Gold and Platinum, we could write the following Hamcrest assertion:
assertThat(member.getUnachievedStatuses(), hasItems(GOLD,PLATINUM));
In FestAssert, the equivalent would be similar:
assertThat(member.getUnachievedStatuses()).contains(GOLD,PLATINUM);
These libraries also allow more complex expressions. For example suppose we wanted to retrieve the ages of all frequent flyers under the age of 18. We could express this quite elegantly in Hamcrest like this:
List memberAges = …;
assertThat(memberAges, everyItem(lessThan(18)));
We could also do something similar in NUnit:
Assert.That(memberAges, Has.All.LessThan(18));
Traditionally, higher-level assertions like this would often require loops and non-trivial logic in the unit tests. This is not only risky, it is also often enough to discourage developers from testing non-trivial outcomes. In this way, by making it easier for developers to express their expectations effectively, fluent assertion libraries contribute to more meaningful and higher quality executable specifications.
Hamcrest and FestAssert both play a similar role in Java-based BDD. Hamcrest is more flexible and easier to extend, but FestAssert has a simpler syntax and is a little easier to use. The constraint-based assert model in NUnit is similar, and has a particularly rich library of assertions. All of these are a vast improvement on the traditional Assert statements.
Fluent assertion libraries are in no way specific to BDD, and can be used to make any unit tests easier to understand. However their emphasis on readability, expressiveness and communication make them well aligned with the BDD philosophy.
This is a shorted extract from BDD in Action, to be released in Summer 2014 and currently available through MEAP (Manning Early Access Program).
Opinions expressed by DZone contributors are their own.
Trending
-
Managing Data Residency, the Demo
-
Decoding eBPF Observability: How eBPF Transforms Observability as We Know It
-
The Role of AI and Programming in the Gaming Industry: A Look Beyond the Tables
-
TDD vs. BDD: Choosing The Suitable Framework
Comments