Comparing FSpec to NSpec

NSpec is another context/specification framework that has been available
for some time. I will here try to compare an example between NSpec and FSpec.

This example is taken from the NSpec web site.

class describe_contexts : nspec
{
    // context methods require an underscore. For 
    // more info see DefaultConventions.cs.
    void describe_Account()
    {
        // contexts can be nested n-deep and contain 
        // befores and specifications
        context["when withdrawing cash"] = () =>
        {
            before = () => account = new Account();
            context["account is in credit"] = () =>
            {
                before = () => account.Balance = 500;
                it["the Account dispenses cash"] = () =>
                    account.CanWithdraw(60).should_be_true();
            };
            context["account is overdrawn"] = () =>
            {
                before = () => account.Balance = -500;
                it["the Account does not dispense cash"] = () =>
                    account.CanWithdraw(60).should_be_false();
            };
        };
    }
    private Account account;
}

Let’s see how the account example could look using FSpec:

let specs =
  describe "Account" [
    subject (fun _ -> new Account())

    context "when withdrawing cash" [
      context "account is in balance" [
        before (fun ctx -> 
            ctx.GetSubject<Account>().Balance <- 500)

        it "account dispenses cash" <| fun ctx ->
          ctx.GetSubject<Account>().CanWithdraw(60).Should be.True
      ]

      context "account is overdrawn" [
        before (fun ctx -> 
            ctx.GetSubject<Account>().Balance <- -500)

        it "the Account does not dispense cash" <| fun ctx ->
          ctx.GetSubject<Account>().CanWithdraw(60).Should be.False
      ]
    ]
  ]

This direct translation to FSpec may not seem much better than the original
NSpec example.

But there is room for improvement as this example has code duplication.

In the two different nested contexts, the same code is used to set up the
balance; only the actual value is different between the two contexts.

That is exactly that type of problem that the FSpec’s metadata is trying to
solve. Let’s rewrite to take advantage of that:

let specs =
  describe "Account" [
    subject (fun ctx -> 
      let account = new Account()
      account.Balance <- ctx.metadata?initial_balance
      account)

    describe ".CanWithdraw()" [
      subject <| fun ctx -> ctx.GetSubject<Account>().CanWithdraw(60)

      ("initial_balance" ++ 500) ==>
      context "account is in balance" [
        it "account dispenses cash" <| fun ctx ->
          ctx.Subject.Should (be.True) 
      ]

      ("initial_balance" ++ -500) ==>
      context "account is overdrawn" [
        it "the Account does not dispense cash" <| fun ctx ->
          ctx.Subject.Should (be.False)
      ]
    ]
  ]

Now the code duplication has been removed. The two different contexts specify
only exactly what is different in the two contexts, the initial balance.

The syntax for setting up metadata may change, when a more natural syntax is
discovered.

But FSpec has another trick up it’s sleeve; one liner verifications:

// Add extension methods to the test context to
// help the tests become easier to read
type TestContext
  with
    member self.Account 
      with get() : Account = self?account
      and set account = self?account <- account

let specs =
  describe "Account" [
    before (fun ctx -> 
      ctx.Account <- new Account()
      ctx.Account.Balance <- ctx.metadata?initial_balance)

    describe ".CanWithdraw()" [
      subject (fun ctx -> 
        ctx.Account.CanWithdraw(60))

      ("initial_balance" ++ 500) ==>
      context "when account is in balance" [
        itShould be.True
      ]

      ("initial_balance" ++ -500) ==>
      context "when account is overdrawn" [
        itShould be.False
      ]
    ]
  ]

Running the above example results in the following output:

simple-account-spec-output

This was just a little taste of how FSpec compares to NSpec, and how FSpec aims
to remove language noise, allowing the specs to be described cleanly and
with as little duplication as possible.