NExpect

An assertions framework for .NET with a BDD-like feel, inspired by Chai and Jasmine, designed to be user-extensible

View on GitHub

In previous posts, I’ve examined how to do simple and collection-based assertions with NExpect

These have enabled two of the design goals of NExpect:

Now, we come on to the third goal, inspired by Jasmine: easy user extension of the testing framework to facilitate expressive testing of more complex concepts.

Most of the “words” in NExpect can be “attached to” with extension methods. So the first question you have to ask is “how do I want to phrase my assertion?”. You could use the already-covered .To or .To.Be:

internal static class Matchers
{
  internal static void Odd(
    this IBe<int> be
  )
  {
    be.AddMatcher(actual =>
    {
      var passed = actual % 2 == 1;
      var message = passed
                    ? $"Expected {actual} not to be odd"
                    : $"Expected {actual} to be odd";
      return new MatcherResult(
        passed,
        message
      );
    });
  }
}

The above extension enables the following test:

[Test]
public void ILikeOddNumbers()
{
  // Assert
  Expect(1).To.Be.Odd();
  Expect(2).Not.To.Be.Odd();
}

There are a few concepts to unpack:

.AddMatcher()

This is how you add a “matcher” (term borrowed from Jasmine… Sorry, I couldn’t come up with a better name, so it stuck) to a grammar continuation like .To or .Be. Note that we just create an extension method on IBe<T> where T is the type you’d like to test against, and the internals of that extension basically just add a matcher. This takes in a Func<T, IMatcherResult> so your actual matcher needs to return an IMatcher result, which really is just a flag about whether or not the test passed and the message to display if the test failed.

Pass or fail?

This is the heart of your assertion and can be as tricky as you like. Obviously, your matcher could also have multiple exit strategies with specific messages about each possible failure. But the bit that takes a little getting used to is that you’re writing a matcher which could be used with .Not in the grammar chain, so you should cater for that eventuality.

Meaningful messages

There’s a simple strategy here: get the passed value as if you’re doing a positive assertion (ie, as if there is no .Not in the chain) and set your message as follows:

It turns out that (mostly), we can write messages like so:

internal static class Matchers
{
  internal static void Odd(
    this IBe<int> be
  )
  {
    be.AddMatcher(actual =>
    {
      var passed = actual % 2 == 1;
      var message =
        $"Expected {actual} {passed ? "not " : ""}to be odd";
      return new MatcherResult(
        passed,
        message
      );
    });
  }
}

Doing this is tedious enough that NExpect offers a .AsNot() extension for booleans:

internal static class Matchers
{
  internal static void Odd(
    this IBe<int> be
  )
  {
    be.AddMatcher(actual =>
    {
      var passed = actual % 2 == 1;
      var message =
        $"Expected {actual} {passed.AsNot()}to be odd";
      return new MatcherResult(
        passed,
        message
      );
    });
  }
}

Also, NExpect surfaces a convenience extension method for printing values: .Stringify() which will:

Use as follows:

internal static class NumberMatchers
{
  internal static void Odd(
    this IBe<int> be
  )
  {
    be.AddMatcher(actual =>
    {
      var passed = actual % 2 == 1;
      var message =
        $"Expected {actual.Stringify()} {passed.AsNot()}to be odd";
      return new MatcherResult(
        passed,
        message
      );
    });
  }
}

You’ll have to think (a little) about your first matcher, but it starts getting easier the more you write (:

Now you can write more meaningful tests like those in the demo project

The above is fine if, like me, you can see a pattern you’d like to test for really easily (if you have a kind of “matcher-first” mindset), but does provide a minor barrier to entry for those who like to write a few tests and refactor out common assertions.

Not to worry: NExpect has you covered with .Compose():

internal static class PersonMatchers
{
  internal static void Jane(
    this IA<Person> a
  )
  {
     a.Compose(actual =>
     {
        Expect(actual.Id).To.Equal(1);
        Expect(actual.Name).To.Equal("Jane");
     });
  }

// ....
  [Test]
  public void TestJane()
  {
    // Arrange
    var person = new Person() { Id = 1, Name = "Jane", Alive = true };

    // Assert
    Expect(person).To.Be.A.Jane();
  }
}

.Compose uses [CallerMemberName] to determine the name of your matcher and attempts to throw a useful UnmetExpectationException when one of your sub-expectations fails. You may also provide a Func to .Compose to generate a more meaningful message.

These are some rather simple examples – I’m sure you can get much more creative! I know I have (:

Some parts of NExpect are simply there to make fluent extension easier, for example:

NExpect will be extended with more “grammar parts” as the need arises. If NExpect is missing a “grammar dangler” that you’d like, open an issue on GitHub