Mercury

Fluent NUnit test extensions

View project on GitHub

Mercury

NuGet Version NuGet Downloads Build Status

Fluent NUnit specification extensions that will run under NCrunch, and ReSharper test runners.

Get started

NuGet

install-package mercury -pre

Inherit

Inherit from MercurySuite and call Specs += for each new spec.

using Mercury;
using NUnit.Framework;

namespace MercuryExample
{
    public class MyTest : MercurySuite
    {
        protected override void Specifications()
        {
            Specs += //see below on writing specs
        }
    }
}

Write specifications

You can copy and paste these examples directly into the array then run the tests with your favorite test runner.

Single assert, no setup

Simplest spec is just a name and an assert:

Specs += "Simple assert".Assert(() => Assert.AreEqual(2, 1 + 1));

This will emit a single unit test with the text "Simple assert" included in the test name.

Arrange

Next most complex is an Arrange, this is equivalent to an NUnit [SetUp] and runs for each Assert,With combination (With's are explained later):

Specs += "New List"
   .Arrange(() => new List<int>())
   .Assert("is empty", list => Assert.AreEqual(0, list.Count));

Classes under test whose constructors do not take parameters can take advantage of this shorter syntax:

.Arrange<StringBuilder>())

If you do not require a test context, you can use Arrange() with no params or types:

Specs += "No context needed because acting on static method"
    .Arrange()
    .Act(() => string.Join(",", "a", "b"))
    .Assert(joined => Assert.AreEqual("a,b", joined));

Act

You can separate out the Act from the Assert. Here the act invokes Any() and the result is passed to the Assert.

Specs += "New List; linq says there is not any"
   .Arrange<List<int>>()
   .Act(list => list.Any())
   .Assert(any => Assert.IsFalse(any));

Or more succinctly in this case:

Specs += "New List; linq says there is not any"
   .Arrange<List<int>>()
   .Act(list => list.Any())
   .Assert(Assert.IsFalse);

ActOn

Act returns the result of it's method. This will not work if the method is void. ActOn is also way to keep the test context. Example:

Specs += "Act version"
   .Arrange(() => new List<int>(new []{1, 2, 3}))
   .Act(list =>
   {
     list.Clear();
     return list;
   })
   .Assert(list => Assert.AreEqual(0, list.Count));

Can be just:

Specs += "ActOn version"
   .Arrange(() => new List<int>(new []{1, 2, 3}))
   .ActOn(list => list.Clear())
   .Assert(list => Assert.AreEqual(0, list.Count));

With

With enables you to parameterise your tests. It takes between 1 and 5 generic parameters. As you can set up anoymous, you can usually get away with just one and then you also get useful names.

Specs += "When I add an item to list"
   .Arrange<List<int>>()
   .With(new {a=1})
   .ActOn((list, data) => list.Add(data.a))
   .Assert("it is exactly one long",
      (list, data) => Assert.AreEqual(1, list.Count));

With can be used in a situation without a test context using Arrange()

Specs += "Test-Context less using with"
    .Arrange()
    .With(new {a = "a", b = "b", expect = "a,b"})
    .With(new {a = "c", b = "d", expect = "c,d"})
    .Act(data => string.Join(",", data.a, data.b))
    .Assert((actual, data) => Assert.AreEqual(data.expect, actual));

With when used with mutiple data items gives more assert styles:

Specs += "Multiple data arguments in with, and three assert styles"
    .ArrangeNull()
    .With("a", "b", "a,b")
    .With("c", "d", "c,d")
    .Act((_, a, b, expected) => string.Join(",", a, b))
    .Assert((result, a, b, expected) => Assert.AreEqual(expected, result))
    .Assert((result, expected) => Assert.AreEqual(expected, result))
    .AssertEqualsExpected();

Multiple Withs and parameter injection to test name

Use the # symbol to inject named parameters from your With data type.

Specs += "When I add #a item to list"
   .Arrange<List<int>>()
   .With(new {a=1})
   .With(new {a=2})
   .ActOn((list, data) => list.Add(data.a))
   .Assert("it is exactly one long",
      (list, data) => Assert.AreEqual(1, list.Count));

This emits two tests:

When I add 1 item to list it is exactly one long
When I add 2 item to list it is exactly one long

Multiple Withs and Asserts

The total number of tests emitted is the number of Asserts multiplied by the number of Withs. This test below therefore runs 4 tests.

Specs += "When I add #a to list"
   .Arrange<List<int>>()
   .With(new {a=1})
   .With(new {a=2})
   .ActOn((list, data) => list.Add(data.a))
   .Assert("it is exactly one long",
      (list, data) => Assert.AreEqual(1, list.Count))
   .Assert("it contains #a",
      (list, data) => Assert.AreEqual(data.a, list[0]));

Place expected values in the data

Where each With will generate a different expected value, include those expected values in the With data.

Specs += "When I add #expectedLength items to list"
   .Arrange<List<int>>()
   .With(new {a=new []{1,2,3},   expectedLength=3, expectedSum=6})
   .With(new {a=new []{4,6,7,9}, expectedLength=4, expectedSum=26})
   .ActOn((list, data) => list.AddRange(data.a))
   .Assert("it is exactly #expectedLength long",
      (list, data) => Assert.AreEqual(data.expectedLength, list.Count))
   .Assert("the sum is #expectedSum",
      (list, data) => Assert.AreEqual(data.expectedSum, list.Sum()));

MercurySuite advantages

class SpecByMethodExample : MercurySuite
{
    protected override void Specifications()
    {
        Specs += "Example of spec defined in method".Assert(() => Assert.AreEqual(2, 1 + 1));

        Specs += "Lets you space out tests".Assert(() => Assert.AreEqual(2, 1 + 1));

        for (int i = 0; i < 10; i++)
        {
            Specs += "And even lets you create specs dynamically #i"
                .Arrange()
                .With(new {i})
                .Act(data => data.i*10)
                .Assert((result, data) => Assert.IsTrue(result%10 == 0));
        }
    }
}