Spec is a small testing framework that implements bdd style testing for .net. it is mainly inspired by mochajs and jasmine. The library takes a minimal low ceremony approach to define your tests and allows for using a Arrange-Act-Assert pattern in your unit testing;
Install the nuget package in your project. Open the Package manager console and type:
PM> Package-Install spec.core
This will install the Spec
base class that allows you to write your tests.
In order to be able to run the tests you need to install the vsix extension. You can downlad the Spec.TestAdapter here. After you instal the vsix file, your tests should be discovered by the Visual Studio Test Runner.
The Spec TDD library works by creating any class that inherits from the Spec
base type.
Then you define your specs in the class constructor. You can have describe blocks, context blocks, and it to define specifications.
public class SimpleTest : Spec
{
public SimpleTest()
{
describe("A describe suite", ()=>{
var scopeVariable = false;
it("can have multiple specs", () =>
{
Assert.IsTrue(true);
});
xit("or disabled specs", () =>
{
Assert.IsFalse(true);
});
});
}
}
Also you can use hooks for BeforeAll, BeforeEach, AfterAll & AfterEach.
public class SimpleTest : Spec
{
public SimpleTest()
{
describe("A describe suite", ()=>{
var scopeVariable = false;
beforeEach(() =>
{
scopeVariable = true;
});
it("scope variable should be set in each run", () =>
{
Assert.IsTrue(true);
});
afterEach(() =>
{
scopeVariable = false;
});
});
}
}
BeforeAll and AfterAll hooks will be run once per describe, while beforeEach and AfterEach will run once per every it. Also you can have multiple hooks per spec, which will be appended and executed as discovered.
namespace SampleSpecs
{
public class AfterEachSubjectTest : Spec
{
public AfterEachSubjectTest()
{
describe("#Test AfterEach being called only once",()=>
{
List<int> testList = null;
context("The beforeEach suite should consider being called for every spec", () =>
{
beforeAll(() =>
{
testList = new List<int>();
testList.Add(1);
});
beforeEach(() =>
{
testList.Add(2);
});
it(" [it] List should have 2 items", () =>
{
testList.Count.Should().Be(2);
});
afterEach(() =>
{
testList = null;
});
});
it(" [it] should be null", () =>
{
testList.Should().BeNull();
});
});
}
}
}
Spec does not include an assertion library, you can use whatever assertion library you feel more comfortable with,
- Standard Assert Library
- Fluent Assertions
- Shouldy
- Expect better
- NFluent
describe("Multiple Assertion Libraries", () =>
{
context("There are many assertion libraries out there:", () =>
{
it("Fluent Assertions", () =>
{
new[] { 1, 5, 3 }.Any(x => x == 1).Should().BeFalse("Because it should fail");
});
it("Assert", () =>
{
Assert.IsFalse(true, "this is the regular assert library - failing ");
});
it("Expect", () =>
{
Expect.The(true).ToBeFalse();
});
it("Shouldy", () =>
{
true.ShouldBe(false);
});
it("NFluent", () =>
{
Check.That(true).IsFalse();
});
}
)};
Note: Pretty much anything that throws an exception should work.
Async/Await methods can be tested by making the it lamba async, and awaiting on the applicable method.
it("it should be async ", () => async
{
var sut = new Class();
var result = await sut.asyncMethod();
result.Should().BeTrue();
});
In async/await, exceptions are a bit harder to catch because they run in a separate application thread. In order to catch them you must wrap them in a Func and then execute it, otherwise the test runner fails and exits.
it("An async exception has to be catched with a special idiom", () =>
{
var sut = new Class();
Func<Task> a = async () => { await sut.asyncMethodException(); };
a.ShouldThrow<Exception>();
});
Spec supports creating it statements inside a List, this way you can add different test cases and have them run with fixture data.
describe("Dynamic spec creation", () =>
{
new List<int>(){0,2,4}.ForEach(i =>
{
it("it should be dynamic " + i.ToString(), () =>
{
System.Threading.Thread.Sleep(1000);
(i%2).Should().Be(0);
});
});
});
Because the spec runner loads types that inherit the spec type, you can create your own base clases that implement specific behaviors that you want to abstract. You can even write regular hooks in the root class that will be executed as part of the spec.
Spec also supports a TDD interface by inheriting from the Test base class.The Tdd Interface maps describe to suite, and test to it.Setup maps to beforeEach and tearDown to afterEach.
public class TddStyle : Test
{
public TddStyle()
{
suite("To create a TDD Suite", () =>
{
string subject = string.Empty;
setup(() =>
{
subject = "initialized";
});
test("test subject is initialized", ()=>
{
subject.Should().Be("initialized");
});
});
}
}
Because the spec framework uses Action<>
to define the tests, the following extensions allow for an better IDE experience to outline describe, context and it, plus providing better visual support for indentation.