Skip to content
Cyrille DUPUYDAUBY edited this page May 10, 2018 · 20 revisions

Writing your own checks

You can write your own checks, either to add checks to already supported classes or to add checks to your own classes. Your own checks will integrate with the existing syntax, including support for And and Not keywords, if you respect a set of simple rules.

We will build a check step by step. It will check if a string represents some integer.

Prerequisite: you must be familiar with extension methods

General Signature

Checks must follow a set of rules. string is a placeholder for the actual type you want to write checks for.

  1. It must be an extension method to the ICheck interface.
  2. Return type should be ICheckLink<ICheck>.

Hence, the signature of our looks like:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
...
}

And can be used like that:

var sut = "12";
...
Check.That(sut).IsThisNumber(14);

Their can be variations for the return type, to support advanced syntax, but let's them put aside for now.

Return value

The role of the return value is to support chaining of checks using And. There is an function that will help you build the proper instance: ExtensibilityHelper.BuildChainLink(...). It needs a single argument, which is the ICheck instance received as the first argument. Your code should like this then:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  return ExtensibilityHelper.BuildChainLink(check);
}

Now your code compile and can run. It does not check anything for course, but you can use it with And, such as:

var sut = "12";
...
Check.That(sut).IsThisNumber(14).AndIsNull();

This test will fail, as sut is clearly not null. Now it is time to perform actual checks.

Writing check logic

There is an helper interface, using a fluent API, that you should use to write the checking logic: ICheckLogic. First, you need to build an ICheckLogic instance. There is an helper function for that: ExtensibilityHelper.BeginCheck It needs a single argument, which is the ICheck instance received as the first argument. So, your code now looks like that:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check);
  return ExtensibilityHelper.BuildChainLink(check);
}

You use ICheckLogic by specifying a set of constraints/rules in a chained fashion and you close the chain using EndCheck.

EndCheck is the method that evalute constraints and rules and report the errors if the check failed.

The code is now:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check)
   .EndCheck();
  return ExtensibilityHelper.BuildChainLink(check);
}

Still nothing is performed, so this check will always succeed. Let's start by taking care of the null case, i.e. we want this check to fail:

 string nullString = null;
 Check.That(nullString).IsThisNumber(14);

There is a constraint for that: FailIfNull(...) The code is now:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check)
   .FailsIfNull()
   .EndCheck();
  return ExtensibilityHelper.BuildChainLink(check);
}

And now, the check fails, providing an error message.

The checked string is null.

We can now handle the rule we want to evaluate, now we need FailsWhen(...) with which we can specify our constraint (as a lambda) and the error message when it fails. The code is now:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check)
   .FailsIfNull()
   .FailsWhen( sut => sut != expected.ToString(), "The {0} does not contain the {1}.")
   .EndCheck();
  return ExtensibilityHelper.BuildChainLink(check);
}

In the error message, {0} and {1} are used as placeholders for the sut and the expected value. NFluent will determine what should be the proper expression to fill those place holders, depending on type(s) and value(s) considered.

So let's see how it works with a failing test:

 Check.That("12").IsThisNumber(14);

We get the following message.

The checked string does not contain the expected one.
The checked string:
	["12"]

As you can see, we are halfway there. NFluent properly indentified we were checking a string, but the error message assumes the expected value is another string (expected one) and provides no information on it.

We need to specify what is the expected result, using the Expecting(...) method:

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check)
   .FailsIfNull()
   .FailsWhen( sut => sut != expected.ToString(), "The {0} does not contain the {1}.")
   .Expecting(expected)
   .EndCheck();
  return ExtensibilityHelper.BuildChainLink(check);
}

So let's see how it works with a failing test:

 Check.That("12").IsThisNumber(14);

We get the following message.

The checked string does not contain the expected value.
The checked string:
	["12"]
The expected value:
	[14]

Now we have a good looking error message. We need to verify the check runs ok when we have the correct value:

 Check.That("14").IsThisNumber(14);

No exception raised. Great! But we are not done yet.

Handling negation

You need to remember that NFluent offers a Not keyword that can be used to invert the checking logic. So let's see what happens when we try to use it.

So let's see how it works with a negated failing test:

 Check.That("12").Not.IsThisNumber(14);

No exception raised, the check is successful, as we expected.

What happens if we negate a successful check?

 Check.That("14").Not.IsThisNumber(14);

Oups, we get an InvalidOperationException

{System.InvalidOperationException}: 'Error message was not specified.'

Of course, we need to provide an exception message for the negated case. We must use the method Negates(...) for that.

public static ICheckLink<ICheck<string>> IsThisNumber(this ICheck<string> check, int expected)
{
  ExtensibilityHelper.BeginCheck(check)
   .FailsIfNull()
   .FailsWhen( sut => sut != expected.ToString(), "The {0} does not contain the {1}.")
   .Expecting(expected)
   .Negates("The {0} contains the {1} whereas it should not.")
   .EndCheck();
  return ExtensibilityHelper.BuildChainLink(check);
}

Now we get an error message:

The checked string contains the given value whereas it should not.
The checked string:
	["14"]
The expected value: different from
	[14]

We have an error message.