NExpect

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

View on GitHub

Getting started

To get started using NExpect, you will need to:

using static NExpect.Expectations;
using NExpect;

[TestFixture]
public class MyTextFixture
{
  [Test]
  public void MyTest()
  {
    // positive assertion
    Expect(1).To.Equal(1);
    // negative assertion
    Expect(1).Not.To.Equal(2);
    // alternative negative
    Expect(1).To.Not.Equal(2);
  }
}

Primitives

Testing primitive values (or primitive properties) is quite simple:

Booleans

Expect(true).To.Be.True();
Expect(false).To.Be.False();
Expect(true).Not.To.Be.False();
Expecct(false.Not.To.Be.True();

Numbers

Expect(1).To.Equal(1);
// alternative
Expect(1).To.Be.Equal.To(1);
Expect(2).To.Be.Greater.Than(1);
Expect(2).To.Be.Greater.Than.Or.Equal.To(2);
Expect(1).To.Be.Less.Than(2);
Expect(1).To.Be.Less.Than.Or.Equal.To(1);
Expect(1)
  .To.Be.Greater.Than(0)
  .And.Less.Than(2);
Expect(1)
  .To.Be.Greater.Than.Or.Equal.To(1)
  .And.Less.Than(10);

NExpect is strongly typed, so you may find times when you’re trying to compare “upward”. This happens if you start the Expect() with a lower-bit numeric (eg short) and compare with a higher-bit numeric (eg long). You can work around this by up-casting the original call to Expect():

short result = 1;
Expect((int)result).To.Equal(1);

Strings

There are basic tests for

Expect("hello").To.Equal("hello");
Expect("hello").To.Start.With("he");
Expect("hello").To.Contain("ell");
Expect("hello").To.End.With("o");
Expect("hello").Not.To.Be.Null();
Expect("hello").Not.To.Be.Null.Or.Empty();
Expect("hello").Not.To.Be.Null.Or.Whitespace();

In addition, NExpect string assertions can fluently test multiple parts of a string. For example, it might be useful to know that a string starts with one substring, contains another, and then another, and then ends with another:

var result = "NExpect is a fluent, expressive assertions framework for .net";
Expect(result)
  .To.Start.With("NExpect")
  .And.Contain("fluent")
  .Then("expressive")
  .Then("framework")
  .And.End.With(".net");

This style is often easier to read and write than using regular expressions. However, you can still use regular expressions:

var result = "Regular expressions are powerful";
// you can provide a Regex object:
Expect(result).To.Match(new Regex("^Regular"));
// or let NExpect compile one for you
Expect(result).To.Match("^Regular");

DateTime values

NExpect can perform assertions against DateTime values in much the same way you’d expect to perform numeric assertions:

var today = DateTime.Now;
var yesterday = DateTime.Now.AddDays(-1);
var tomorrow = DateTime.Now.AddDays(1);

Expect(today).To.Be.Less.Than(tomorrow);
Expect(today).To.Be.Greater.Than(yesterday)
    .And.Less.Than.Or.Equal.To(tomorrow);

By default, NExpect takes into account all properties of DateTime values, including the Kind. So a UTC DateTime could never equal a Local or Unspecified value. Normally, this is what you want – the Kind changes the absolute value of the DateTime value. However, if this is not what you want, this behavior can be overridden by setting the environment variable DEEP_EQUALITY_IGNORES_DATETIME_KIND to any of the following (case-insensitive):

NExpect also allows for approximate DateTime comparison, which can be useful when you’re looking for DateTime values which are “close-enough”:

var first = DateTime.Now;
Thread.Sleep(1);
var second = DateTime.Now;
Expect(first).To.Approximately.Equal(second);

This approximation defaults to mean “within a second of each other”, but you can control this behavior by providing another argument, a TimeSpan which specifies the maximum allowable drift:

var first = DateTime.Now;
Thread.Sleep(1500);
var second = DateTime.Now;
Expect(first).To.Approximately.Equal(second, TimeSpan.FromSeconds(2));

Objects

NExpect has rich support for complex object assertions:

Reference equality

Expect(new {}).Not.To.Be(new {});

Deep equality

This is most useful when there are multiple assertions which would have to be made to prove that two objects are equivalent:

[TestFixture]
public class DeepEqualityTesting
{
  public class Person
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }
  public class Entity
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }

  [Test]
  public void TestDeepEquality
  {
    var left = new Person()
    {
      Id = 1,
      Name = "bob",
      Description = "123"
    };
    var right = new Entity()
    {
      Id = 1,
      Name = "bob",
      Description = "123";
    }
    
    // incoming types are not important, properties are matched by name and type
    Expect(left).To.Deep.Equal(right);
    // since the types don't matter, we can use anonymous types:
    Expect(left).To.Deep.Equal(new
    {
      Id = 1,
      Name = "bob",
      Description = "123"
    });
  }
}

Deep equality testing traverses all the way down through all properties, so even complex properties are compared:

Expect(new { Child = new { id = 1 } })
  .To.Deep.Equal(new { Child = new { id = 1 } });

Intersection Equality

Sometimes, we’re only interested in a subset of properties, for example when adding a record to the database, where some fields are auto-generated (eg timestamps) and are not crucial to the test at hand:

public class DatabaseEntity
{
  // this could be an auto-incrementing key
  public int Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
  // this would be auto-generated at the database
  public DateTime Created { get; set; }
}

public class EntityRepository
{
  public DatabaseEntity Create(string name, string description)
  {
    // inserts data
    // returns the full database object
  }
}

[TestFixture]
public class TestingIntersectionEquality
{
  
  [Test]
  public class ShouldSaveANewEntity()
  {
    var repository = new EntityRepository();
    var result = repository.Create("Durban", "City in South Africa");
    Expect(result).To.Intersection.Equal(new
    {
      Name = "Durban",
      Description = "City in South Africa"
    });
  }
}

Collections

We can perform basic testing against sizes of collections:

Expect(collection).To.Be.Empty();
// collection must contain exactly, and only, 3 items
Expect(collection).To.Contain.Only(3).Items();

Collections of primitives

Expect(strings)
  .To.Contain.Exactly(1)
  .Equal.To("abc");
// we can use other string assertions
Expect(strings)
  .To.Contain.Exactly(1)
  .Starting.With("foo");
// should have some even numbers in the collection
Expect(numbers)
  .To.Contain.Any()
  .Matched.By(i => i % 2 == 0);
// collection should have exactly one member, equal to 42
Expect(numbers)
  .To.Contain.Only(1)
  .Equal.To(42);

Collections of objects

Expect(objects)
  .To.Contain.Exactly(2)
  .Deep.Equal.To(expected);
Expect(objects)
  .To.Contain.Any()
  .Intersection.Equal.To(expected);

Dictionaries

var dict = new Dictionary<string, string>();
dict["foo"] = "bar";
Expect(dict).To.Contain.Key("foo")
  .With.Value("bar");

Exceptions

Asserting no exception thrown

Expect(() => { } ).Not.To.Throw();

Asserting anything was thrown

// expect to throw anything
Expect(() => throw new Exception("die"))
  .To.Throw();

Asserting a specific exception was thrown

// expect a specific exception
Expect(() => throw new InvalidOperationException("uh-oh"))
  .To.Throw<InvalidOperationException>();
// useful for TestCases, etc
Expect(() => throw new ArgumentException("moo"))
  .To.Throw().With.Type(typeof(ArgumentException));

Asserting against properties on any exception

Expect(() => throw new ArgumentException("bad wolf"))
  .To.Throw()
  .With.Message.Containing("wolf");

Asserting against properties on specific exceptions

Expect(() => throw new ArgumentException("name"))
  .To.Throw<ArgumentException>()
  .With.Message.Containing("name");
Expect(() => throw new CustomException("stuff"))
  .To.Throw<CustomException>()
  .With.Property(e => e.MoreInformation)
  .Equal.To("happens only at night");

Extending NExpect

NExpect extension was originally inspired by Jasmine.

Composing Expectations (ie: refactoring blocks of Expectations)

A “lower-hanging branch” of extension was added to facilitate easier extension from a refactor: Composition. Messages from Composition may not be as specific as from going all the way into adding matchers, but they can be “good enough” and are quicker to grasp and produce. If we were to start with this block of assertions, and imagine that the same assertions are found in several tests, so it would be worthwhile to have a matcher to help out:


var obj = Person()
{
  Id = 1,
  Name = "Billy Bob",
  DateOfBirth = new DateTime(1972, 1, 13)
};
Expect(obj.Id)
  .To.Be.Greater.Than(0, () => "Invalid Id");
Expect(obj.Name)
  .Not.To.Be.Null.Or.Empty(() => "Invalid Name");
// should start with an upper-case letter
// (this could be it's own custom matcher too!)
Expect(obj.Name[0].ToString().ToUpper())
  .To.Equal(obj.Name[0].ToString());
Expect(obj.DateOfBirth)
  .To.Be.Greater.Than(new Date(1970, 1, 1), () => "Invalid date of birth");

It might be convenient if we could replace that with:

Expect(obj).To.Be.Valid();

We would start by mousing-over the .Be of the first expectation, to see the type – we’re going to write an extension method! We should see that the .Be is of type IBe<Person>, so we can make up an extension method like so:

public static class PersonMatchers
{
  public static void Valid(this IBe<Person> be)
  {
    be.Compose(actual => // the actual value being tested is passed in
    {
      Expect(actual.Id)
        .To.Be.Greater.Than(0, () => "Invalid Id");
      Expect(actual.Name)
        .Not.To.Be.Null.Or.Empty(() => "Invalid Name");
      // should start with an upper-case letter
      // (this could be it's own custom matcher too!)
      Expect(actual.Name[0].ToString().ToUpper())
        .To.Equal(actual.Name[0].ToString());
      Expect(actual.DateOfBirth)
        .To.Be.Greater.Than(new Date(1970, 1, 1), () => "Invalid date of birth");
    });
  }
}

The first expectation to fail in the block will cause the entire expectation to fail with the custom message for that expectation included in the output.

The same method can be used against any dangling verb or noun:

Say, for example, we’d like the PersonMatchers.Valid extension method to tell us everything which is wrong with a Person object, not just the first thing it finds:

public static class PersonMatchers
{
  public static void Valid(this IBe<Person> be)
  {
    be.AddMatcher(actual => // the actual value being tested is passed in
    {
      var hasValidId = actual.Id > 0;
      var hasValidName = !string.IsNullOrEmpty(actual.Name) &&
        actual.Name[0].ToString() == actual.Name[0].ToString().ToUpper();
      var hasValidBirthday = actual.DateOfBirth > new Date(1970, 1, 1);
      var passed = hasValidId &&
        hasValidName &&
        hasValidBirthday;
        
       return new MatcherResult(
        passed,
        () =>
        {
          var errors = new List<string>();
          if (!hasValidId)
          {
            errors.Add("Must have an Id > 0");
          }
          if (!hasValidName)
          {
            errors.Add("Must have a name with the first letter capitalized");
          }
          if (!hasValidBirthday)
          {
            errors.Add("Must have been born after the UNIX epoch");
          }
          return "- " + string.Join("\n- ", errors);
        });
    });
  }
}

Matcher libraries

Some specific libraries of matchers have already been created for your convenience – install them if they are useful to you. If you’d like to publish your own matcher libraries that others might find useful, let me know so I can add them to the list below:

NSubstitute matchers

AspNetCore matchers