Fortunately, NFluent may be extended very easily for your own needs. Whether you want to add a new check method on an already covered type, or you want to add check methods on a type not already covered by the library (one of your specific business domain object type for instance): both are easily feasible.
It's based on .NET extension methods that you may add on ICheck<T>
instances (with T as
the system under test. a.k.a. sut), and a default implementation using a NFluent checker so that
your check will be by default compliant with the 'Not' operator.
Now, let's see together how to make it properly:
To create a new check method, write a test for it (TDD ;-), and then follow the same implementation pattern as the one used for the StartsWith() check (applying on string) presented below:
public static ICheckLink<ICheck<char>> IsALetter(this ICheck<char> check)
{
// Every check method starts by extracting a checker instance from the check thanks to
// the ExtensibilityHelper static class.
var checker = ExtensibilityHelper.ExtractChecker(check);
// Then, we let the checker's ExecuteCheck() method return the ICheckLink<ICheck<T>> result (with T as string here).
// This method needs 2 arguments:
// 1- a lambda that checks what's necessary, and throws a FluentAssertionException in case of failure
// The exception message is usually fluently build with the FluentMessage.BuildMessage() static method.
//
// 2- a string containing the message for the exception to be thrown by the checker when
// the check fails, in the case we were running the negated version.
//
// e.g.:
return checker.ExecuteCheck(
() =>
{
if (!IsALetter(checker.Value))
{
var errorMessage = FluentMessage.BuildMessage("The {0} is not a letter.").For("char").On(checker.Value).ToString();
throw new FluentCheckException(errorMessage);
}
},
FluentMessage.BuildMessage("The {0} is a letter whereas it must not.").For("char").On(checker.Value).ToString());
}
- names of the check methods should be chosen carefully and smartly embrace the intellisense autocompletion mechanism (i.e. the 'dot' experience).
- you should avoid using lambda expressions as check methods arguments (cause writing a lambda expression within an check statement is not really a fluent experience, neither on a reading perspective)
- every assertion method should return A check link, and should throw a FluentAssertionException when failing (to make your favorite unit test framwork fail with a clear status message.
- the message of all the FluentAssertionException you throw should be clear as crystal, but also compliant with the ready-to-be-copied-and-paste-for-arrays-or-collections-initialization-purpose objective of NFluent
- Rui has published a great article about the NFluent extensibility model. Available here on CodeDistillers