Documentation

SubSpec Model

SubSpec is built around the idea of being a declarative test framework. The atomic unit you will use to declare your test cases is a Specification. Each Specification consists of three primitive operations:

PrimitiveExtension MethodDescription
ContextContextEstablishs the context for a test case, e.g. setup the System Under Test (SUT)
ActionDoPerform an operation on the SUT
VerificationAssert/ObserveChecks the Action had the desired effect on the SUT

Instead of performing these primitives inside a Specification, you declare them using delegates that are registered with SubSpec's SpecificationContext. When building a Specification, each of the primitive test operations is associated with a string literal describing it, establishing a pattern that follows this structure:

With context, do Foo and verify Bar.

As you can see below, this is the model SubSpec uses to name the methods used to register primitive test operations with the SpecificationContext, but you should feel free (and encouraged!) to change the schema to suit your preferred style. Eventually, a SubSpec Specification should read like a Specification given in a natural language.

[Specification]
public void PushSpecification()
{
	var stack = default(Stack<string>);
	"Given a new stack".Context(() => stack = new Stack<string>());

	string element = "first element";
	"with an element pushed onto it".Do(() => stack.Push(element));

	"expect the stack is not empty".Assert(() => Assert.False(stack.IsEmpty));
	"expect the stacks Top is the pushed element".Assert(() => Assert.Equals(element, stack.Top));
}

Context

The Context delegate is used to register an operation that will establish the System Under Test your Specification will exercise. Each Specification must have a Context. It is common practice to declare a local variable inside the Specification method that will store the context. Each test operation accessing the context will capture this variable in a closure, so the context can be safely shared among primitives. Don't worry, C#'s lambda syntax hides all the complexity involved:

	var stack = default(Stack<string>);
	"Given a new stack".Context(() => stack = new Stack<string>());

Even though it is not necessary to fully understand closures in order to use SubSpec efficiently, I can wholehartedly recommend learning more about them. Jon Skeet has published a great article on "The Beauty of Closures" explaining the principles and mechanisms behind them on his C# In Depth books website.

Disposable Contexts

If your context operation returns an object implementing IDisposable, SubSpec will pick it up and ensure it is disposed correclty after the test has been executed. There is a sweet litte trick you can employ so you do not have to explitly return an IDisposable from your context operation. In C#, the assignment operator returns the left hand operand, what makes assignments like the following possible:

int a, b;
a = b = 10;

For SubSpecs convenience this means that if the only statement in your context lambda statement is an assignment, it will return IDisosable and automagically register it with SubSpec for disposal:

	var context = default(DisposableContext);
	"Given a disposable context".Context(() => context = new DisposableContext());
	// context will be disposed when its no longer needed

Action

Action is the only optional test primitive. The Action operation is represented by a delegate taking no parameters and returning void (commonly known as the Action delegate).

Verifications

Assert and Observe are the primitve verifications supported by SubSpec. The purpose of a verification is to prove your system under test responded correctly to the Action the Specification performed on it.

Assert

The following illustration depicts the way SubSpec composes the primitive actions of a Specification into test cases. For each Assert a new Context is setup and the Action applied:

[Specification]
public void PushSpecification()
{
	var stack = default(Stack<string>);
	"Given a new stack".Context(() => stack = new Stack<string>());

	string element = "first element";
	"with an element pushed onto it".Do(() => stack.Push(element));

	"the stack is not empty".Assert(() => Assert.False(stack.IsEmpty));
	"the stacks Top is the pushed element".Assert(() => Assert.Equals(element, stack.Top));
}

SubSpec Assert

Observe

Another verification primitive is available in SubSpec called Observe. Observe differs from Assert in that all Observations share the same Context with the Do action applied:

[Specification]
public void PushSpecification()
{
	var stack = default(Stack<string>);
	"Given a new stack".Context(() => stack = new Stack<string>());

	string element = "first element";
	"with an element pushed onto it".Do(() => stack.Push(element));

	"the stack is not empty".Observe(() => Assert.False(stack.IsEmpty));
	"the stacks Top is the pushed element".Observe(() => Assert.Equals(element, stack.Top));
}

SubSpec Assert

Updated

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.