NSpec's response to MSpec. NSpec's response to xUnit, NUnit, MsTest and Fixie

  • Download Gist
0_NSpec's response to MSpec. NSpec's response to xUnit, NUnit, MsTest and Fixie.md
Markdown

Trying to compare the differences between MSpec, xUnit and NSpec. The following examples try to be an apples to apples comparision between the frameworks.

I'd strongly recommend reading this gist first.

Each gist contains a url to the original gist these samples were pulled from. You may find valuable comments there.

I tried to keep spacing and general flow consistent between the gists.

A little bit of elaboration on the gists:

  • the first gist (denoted by 1_) is an MSpec test suite
  • the second gist (denoted by 2_) shows how that exact MSpec test would look in NSpec
  • the third gist (denoted by 3_) shows how to write the the test using xUnit, NUnit, MSTest or Fixie
  • keep in mind that version 3_ (depending on the framework) will contain additional attributes
  • the NUnit flavored framework gist deviated enough from the original to constitute a followup gist
  • the reason behind the deviation is explained here: Test styles and avoiding setup/teardown, take the time to read it
  • the fourth gist (denoted by 4_) shows how you would write the altered (new) [x][MS][N]Unit version of the test, but written in NSpec using classes
  • the fifth gist (denoted by 5_) show another appraoch to writing the altered (new) [x][MS][N]Unit version of the test, but written in NSpec using lambdas
1_mspec.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
//original: https://gist.github.com/benfoster/3845201
[Subject(typeof(Account))]
public class AccountSpecs
{
static Account account;
 
public class When_creating_a_new_account
{
Because of = ()
=> account = new Account();
 
It Should_not_have_any_subscription = ()
=> account.Subscriptions.Count.ShouldEqual(0);
 
It Should_not_have_any_sites = ()
=> account.Sites.Count.ShouldEqual(0);
 
It Should_generate_a_new_api_key = ()
=> account.ApiKeys.First().ShouldNotBeEmpty();
}
 
[Subject(typeof(Account), "Adding sites")]
public class Adding_a_site
{
static Exception exception;
 
public class When_the_site_already_exists
{
Establish ctx = ()
=> account = GetAccountWithSite();
Because of = ()
=> exception = Catch.Exception(() => account.AddSite("sites/1"));
 
It Should_throw = ()
=> exception.ShouldBeOfType<InvalidOperationException>();
}
 
public class When_no_subscriptions_exist
{
Establish ctx = ()
=> account = new Account();
 
Because of = ()
=> account.AddSite("sites/1");
 
It Should_not_add_the_site = ()
=> account.Sites.Count.ShouldEqual(0);
}
 
public class When_no_available_subscriptions_exist
{
Establish ctx = ()
=> account = GetAccountWithSite();
 
Because of = ()
=> account.AddSite("sites/2");
 
It Should_not_add_the_site = ()
=> account.Sites.Count.ShouldEqual(1);
}
 
public class When_an_available_subscription_exists
{
Establish ctx = () =>
{
account = new Account();
account.AddSubscription(GetValidSubscription());
};
 
Because of = ()
=> account.AddSite("sites/1");
 
It Should_add_the_site = ()
=> account.Sites.Count.ShouldEqual(1);
}
}
 
[Subject(typeof(Account), "Validating sites")]
public class Validating_sites
{
static bool result;
public class When_the_specified_site_is_mapped_to_the_account
{
Establish ctx = ()
=> account = GetAccountWithSite();
 
Because of = ()
=> result = account.ValidateSite("sites/1");
 
It Should_return_true = ()
=> result.ShouldBeTrue();
}
 
public class When_the_specified_site_is_not_mapped_to_the_account
{
Establish ctx = ()
=> account = GetAccountWithSite();
 
Because of = ()
=> result = account.ValidateSite("sites/2");
 
It Should_return_false = ()
=> result.ShouldBeFalse();
}
}
 
[Subject(typeof(Account), "Validating subscriptions")]
public class Validating_subscriptions
{
static bool result;
public class When_the_account_has_a_valid_subscription
{
Establish ctx = () =>
{
account = new Account();
account.AddSubscription(GetValidSubscription());
};
 
Because of = ()
=> result = account.HasValidSubscription();
 
It Should_return_true = ()
=> result.ShouldBeTrue();
}
 
public class When_the_account_has_no_valid_subscriptions
{
Establish ctx = () =>
{
account = new Account();
account.AddSite("sites/1");
var subscription = new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow.AddMonths(-13), // expired by one month
new SubscriptionDuration(1, SubscriptionPeriod.Yearly));
 
account.AddSubscription(subscription);
};
 
Because of = ()
=> result = account.HasValidSubscription();
 
It Should_return_false = ()
=> result.ShouldBeFalse();
}
}
 
public class When_resetting_api_key
{
static string currentKey;
 
Establish ctx = () =>
{
account = new Account();
currentKey = account.ApiKeys.First();
};
 
Because of = ()
=> account.ResetApiKey();
 
It Should_generate_a_new_key = ()
=> currentKey.ShouldNotEqual(account.ApiKeys.First());
}
 
private static Subscription GetValidSubscription()
{
return new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow,
new SubscriptionDuration(1, SubscriptionPeriod.Yearly)
);
}
 
private static Account GetAccountWithSite()
{
var account = new Account();
account.AddSubscription(GetValidSubscription());
account.AddSite("sites/1");
return account;
}
}
2_nspec_response_to_mspec.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
//orginal: https://gist.github.com/amirrajan/3845281
class describe_Accounts : nspec
{
Account account;
 
void before_each()
{
account = new Account();
}
 
void when_creating_a_new_account()
{
it["doesn't have any subscriptions"] = () =>
account.Subscriptions.Count.ShouldEqual(0);
 
it["doesn't have any sites"] = () =>
account.Sites.Count.ShouldEqual(0);
 
it["generates a new api key"] = () =>
account.ApiKeys.First().ShouldNotBeEmpty();
}
 
void adding_a_site
{
context["when the site already exists"] = () =>
{
before = () => account = GetAccountWithSite();
act = () => account.AddSite("sites/1");
 
it["throws excepton"] = expect<InvalidOperationException>();
};
 
context["when no subscriptions exist"] = () =>
{
before = () => account.AddSite("sites/1");
 
it["doesn't add the site"] = () =>
account.Sites.Count.ShouldEqual(0);
};
 
context["when no available subscriptiosn exist"] = () =>
{
before = () => account = GetAccountWithSite();
act = () => account.AddSite("sites/2");
 
it["doesn't add the site"] = () =>
account.Sites.Count.ShouldEqual(1);
}
 
context["when available subscriptions exists"] = () =>
{
before = () =>
account.AddSubscription(GetValidSubscription());
 
act = () => account.AddSite("sites/1");
 
it["adds the site"] = () =>
account.Sites.Count.ShouldEqual(1);
};
}
 
bool result;
void validating_sites()
{
before = () => account = GetAccountWithSite();
 
context["when the specified site is mapped to the account"] = () =>
{
act = () =>
result = account.ValidateSite("sites/1");
 
it["is valid"] = () =>
result.should_be_false();
};
 
context["when the specified site is not mapped to the account
{
act = () =>
result = account.ValidateSite("sites/2");
 
it["is invalid"] = () =>
result.should_be_false();
}
}
 
void validating_subscriptions()
{
act = () => result = account.HasValidSubscription();
 
context["when the account has a valid subscription"] = () =>
{
before = () => account.AddSubscription(GetValidSubscription());
 
it["returns true"] = () =>
result.ShouldBeTrue();
};
 
context["when the account has no valid subscriptions"] = () =>
{
before = () =>
{
account.AddSite("sites/1");
var subscription = new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow.AddMonths(-13),
new SubscriptionDuration(1, SubscriptionPeriod.Yearly));
 
account.AddSubscription(subscription);
};
 
it["returns false"] = () =>
result.ShouldBeFalse();
}
}
 
Subscription GetValidSubscription()
{
return new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow,
new SubscriptionDuration(1, SubscriptionPeriod.Yearly)
);
}
 
Account GetAccountWithSite()
{
var account = new Account();
account.AddSubscription(GetValidSubscription());
account.AddSite("sites/1");
return account;
}
}
3_xunit_response_to_nspec.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
//original: https://gist.github.com/jbogard/6690905
//I'd recommend reading the comments/opinions in the original gist
//Using NUnit attributes for this test, keep in mind that MSTest, NUnit, xUnit and Fixie have different
//attributes, Fixie can be set up to not need the attributes at all (I'm not sure if that's available
//out of the box however)
namespace Accounts
{
namespace NewAccounts
{
[TestFixture]
public class When_creating_a_new_account
{
Account account = new Account();
[Test]
public void Does_not_have_any_subscriptions()
{
account.Subscriptions.Count.ShouldEqual(0);
}
[Test]
public void Does_not_have_any_sites()
{
account.Subscriptions.Count.ShouldEqual(0);
}
[Test]
public void Generates_a_new_api_key()
{
account.ApiKeys.First().ShouldNotBeEmpty();
}
}
}
 
namespace adding_a_site
{
[TestFixture]
public class When_the_site_already_exists
{
[Test]
public void ThrowsException()
{
Account account = ObjectMother.GetAccountWithSite();
typeof(InvalidOperationException).ShouldBeThrownBy(() => account.AddSite("sites/1"));
}
}
 
[TestFixture]
public class When_no_subscriptions_exist
{
[Test]
public void Should_not_add_the_site()
{
Account account = new Account();
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(0);
}
}
[TestFixture]
public class When_no_available_subscriptions_exist
{
[Test]
public void Should_not_add_the_site()
{
Account account = GetAccountWithSite();
account.AddSite("sites/2");
account.Sites.Count.ShouldEqual(1);
}
}
[TestFixture]
public class When_available_subscriptions_exist
{
[Test]
public void Should_add_the_site()
{
Account account = new Account();
account.AddSubscription(ObjectMother.GetValidSubscription());
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(1);
}
}
}
 
[TestFixture]
public class validating_sites
{
Account account = ObjectMother.GetAccountWithSite();
 
[Test]
public void When_the_specified_site_is_mapped_to_the_account_should_be_valid
{
account.ValidateSite("sites/1").ShouldBeTrue();
}
 
[Test]
public void When_the_specified_site_is_not_mapped_to_the_account()
{
account.ValidateSite("sites/2").ShouldBeFalse();
}
}
 
[TestFixture]
public class validating_subscriptions
{
[Test]
public void When_the_account_has_a_valid_subscription_should_report_as_valid
{
Account account = new Account();
account.AddSubscription(GetValidSubscription());
 
account.HasValidSubscription().ShouldBeTrue();
}
 
[Test]
public void When_the_account_has_no_valid_subscriptions_should_report_as_invalid
{
Account account = new Account();
account.AddSite("sites/1");
 
var subscription = new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow.AddMonths(-13), // expired by one month
new SubscriptionDuration(1, SubscriptionPeriod.Yearly));
 
account.AddSubscription(subscription);
 
account.HasValidSubscription().ShouldBeFalse();
}
}
 
public static class ObjectMother
{
public Subscription GetValidSubscription()
{
return new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow,
new SubscriptionDuration(1, SubscriptionPeriod.Yearly)
);
}
 
public Account GetAccountWithSite()
{
var account = new Account();
account.AddSubscription(GetValidSubscription());
account.AddSite("sites/1");
return account;
}
}
}
4_nspec_response_to_xunit_version_1.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
//the same structure shown in [N][x][Ms]Test tests above can be provided in NSpec.
namespace Accounts
{
namespace NewAccounts
{
class When_creating_a_new_account : nspec
{
Account account = new Account();
void it_does_not_have_any_subscriptions()
{
account.Subscriptions.Count.ShouldEqual(0);
}
void it_does_not_have_any_sites()
{
account.Subscriptions.Count.ShouldEqual(0);
}
void it_generates_a_new_api_key()
{
account.ApiKeys.First().ShouldNotBeEmpty();
}
}
}
 
namespace adding_a_site
{
class When_the_site_already_exists : nspec
{
void it_throws_exception()
{
Account account = ObjectMother.GetAccountWithSite();
typeof(InvalidOperationException).ShouldBeThrownBy(() => account.AddSite("sites/1"));
}
}
 
class When_no_subscriptions_exist : nspec
{
void it_should_not_add_the_site()
{
Account account = new Account();
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(0);
}
}
class When_no_available_subscriptions_exist : nspec
{
void it_Should_not_add_the_site()
{
Account account = GetAccountWithSite();
account.AddSite("sites/2");
account.Sites.Count.ShouldEqual(1);
}
}
class When_available_subscriptions_exist : nspec
{
void it_should_add_the_site()
{
Account account = new Account();
account.AddSubscription(ObjectMother.GetValidSubscription());
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(1);
}
}
}
 
class validating_sites : nspec
{
Account account = ObjectMother.GetAccountWithSite();
 
void specify_when_site_is_mapped_to_the_account_should_be_valid()
{
account.ValidateSite("sites/1").ShouldBeTrue();
}
 
void specify_when_site_is_not_mapped_to_the_account_should_be_invalid()
{
account.ValidateSite("sites/2").ShouldBeFalse();
}
}
 
class validating_subscriptions : nspec
{
void specify_When_the_account_has_a_valid_subscription_should_report_as_valid()
{
Account account = new Account();
account.AddSubscription(GetValidSubscription());
 
account.HasValidSubscription().ShouldBeTrue();
}
 
void specify_when_the_account_has_no_valid_subscriptions_should_report_as_invalid()
{
Account account = new Account();
account.AddSite("sites/1");
 
var subscription = new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow.AddMonths(-13), // expired by one month
new SubscriptionDuration(1, SubscriptionPeriod.Yearly));
 
account.AddSubscription(subscription);
 
account.HasValidSubscription().ShouldBeFalse();
}
}
 
public static class ObjectMother
{
public Subscription GetValidSubscription()
{
return new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow,
new SubscriptionDuration(1, SubscriptionPeriod.Yearly)
);
}
 
public Account GetAccountWithSite()
{
var account = new Account();
account.AddSubscription(GetValidSubscription());
account.AddSite("sites/1");
return account;
}
}
}
5_nspec_response_to_xunit_version_2.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
//the namespace->class->method organization structure [N][x][Ms]Test
//tests demonstrated can also be done in NSpec.
 
//However, NSpec provides an *additional* structure, the lambda: namespace->class->method->lambda
//Here is the same [N][x][Ms]Test tests, but represented using a class->method->lambda structure
class describe_Accounts : nspec
{
Account account;
 
void before_each()
{
account = new Account();
}
void when_creating_a_new_account()
{
it["does not have any subscriptions"] = () =>
account.Subscriptions.Count.ShouldEqual(0);
 
it["does not have any sites"] = () =>
account.Subscriptions.Count.ShouldEqual(0);
 
it["it generates a new api key"] = () =>
account.ApiKeys.First().ShouldNotBeEmpty();
}
 
void adding_a_site()
{
context["when the site already exists"] = () =>
{
it["throws an exception"] = () =>
{
account = ObjectMother.GetAccountWithSite();
typeof(InvalidOperationException).ShouldBeThrownBy(() => account.AddSite("sites/1"));
};
};
 
context["when no subscriptions exist"] = () =>
{
it["should not add the site"] = () =>
{
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(0);
};
};
 
context["when no available subscriptions exist"] = () =>
{
it["should not add the site"] = () =>
{
account = GetAccountWithSite();
account.AddSite("sites/2");
account.Sites.Count.ShouldEqual(1);
};
};
 
context["when available subscriptions exist"] = () =>
{
it["should add the site"] = () =>
{
account.AddSubscription(ObjectMother.GetValidSubscription());
account.AddSite("sites/1");
account.Sites.Count.ShouldEqual(1);
};
};
}
 
void validating_sites()
{
before = () => account = ObjectMother.GetAccountWithSite();
 
it["when site is mapped to the account, should be valid"] = () =>
account.ValidateSite("sites/1").ShouldBeTrue();
 
it["when site is not mapped to account, shoudl be invalid"] = () =>
account.ValidateSite("sites/2").ShouldBeFalse();
}
 
void validating_subscriptions()
{
it["has a valid subscription, then report as valid"] = () =>
{
account.AddSubscription(GetValidSubscription());
 
account.HasValidSubscription().ShouldBeTrue();
};
 
it["has not valid subscriptions, should report invalid"] = () =>
{
Account account = new Account();
account.AddSite("sites/1");
 
var subscription = new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow.AddMonths(-13), // expired by one month
new SubscriptionDuration(1, SubscriptionPeriod.Yearly));
 
account.AddSubscription(subscription);
 
account.HasValidSubscription().ShouldBeFalse();
};
}
}
 
public static class ObjectMother
{
public Subscription GetValidSubscription()
{
return new Subscription(
"products/1",
"Fabrik Subscription",
69M,
DateTime.UtcNow,
new SubscriptionDuration(1, SubscriptionPeriod.Yearly)
);
}
 
public Account GetAccountWithSite()
{
var account = new Account();
account.AddSubscription(GetValidSubscription());
account.AddSite("sites/1");
return account;
}
}

I'm a fan of less syntax noise when it comes to tests. Recently I've been using XUnit to run some great BDD tests and I find that the trick, for me, is focusing on the syntax so that I am, indeed, testing behavior.

To be a bit more specific: namespace gymnastics and inline delegates make things difficult to read. What I prefer to do is use the file system to my advantage :).

So, here's something that I find useful: Create a directory for the feature you're testing - in this case it would be tempting to say "Accounts" but that's not a feature - that's an aspect of a feature. I would venture to guess that Subscribing is the feature you're going for (so would your tests in these examples) and you want to know what happens when an Account is created as part of a new Subscription.

Seems like a bit too much detail but I think this is more important than the Startup/Teardown discussion - just WTF are we testing and why?

The first file I would pop in there would be "NewSubscription.cs" and in there...

[Trait("New Subscription", "Account Creation")]
public class NewSubscription
{
    //drop this here or in the constructor
    //make this class IDisposable for removal of data if you want
    Account account = new Account();

    [Fact(DisplayName="Subscriptions are empty")]
    public void Does_not_have_any_subscriptions()
    {
        account.Subscriptions.Count.ShouldEqual(0);
    }

    [Fact(DisplayName=""Sites are Empty")]
    public void Does_not_have_any_sites()
    {
        account.Subscriptions.Count.ShouldEqual(0);
     }
     [Fact(DisplayName="Api is created")]
     public void Generates_a_new_api_key()
     {
        account.ApiKeys.First().ShouldNotBeEmpty();
     }
}

Anyway - I find this very readable but I'm not a huge fan of the Attribute noise :). I've grown used to it I spose and it's easy for other devs to figure out.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.