Storyteller provides a couple mechanisms to reuse grammars across fixtures or even within the same fixture:
- Subclass a
Fixture
with inheritance. - Use Embedded Sections
- Use Currying to use an existing grammar in a more specific way. Shown below.
- Use the
Fixture.Import()
method shown below
Currying for More Expressiveness
Let's say that you're working on an invoicing system again. A common task in your Storyteller project will be to set up an Invoice
object with all its various properties. In some cases you definitely want to explicitly specify all of the Invoice's properties. In other cases, the only thing that matters for the specification is that an Invoice is open or fulfilled. Since one of the Storyteller's deepest held ideas is that only information germane to a specification should be expressed in a specification, Storyteller provides a feature called grammar currying to allow you to supply less information to an existing grammar.
The generic grammar for setting up an Invoice and a curried version to set up an open invoice.
public class CurryingFixture : Fixture
{
[FormatAs("Invoice {Id} is open {IsOpen} and due on {DueDate}")]
public void CreateInvoice(string Id, DateTime DueDate, Boolean IsOpen)
{
}
public IGrammar OpenInvoice()
{
return this["CreateInvoice"].Curry()
.Template("Invoice {Id} is open")
.Defaults("DueDate:TODAY+2,IsOpen:true");
}
}
In usage, you can see the difference between the more generic grammar and the specific, curried grammar.
The goal of grammar currying was to make the expression of the specifications more declarative and easier to understand without requiring users to duplicate grammar code.
Importing Grammars
Storyteller also supports a mechanism to import a grammar from one fixture into another:
public class LogoutFixture : Fixture
{
[FormatAs("Log the user out")]
public void Logout()
{
// manipulate the system under test to
// log the user out
}
}
public class AnotherFixture : Fixture
{
public AnotherFixture()
{
this["Logout"] = Import<LogoutFixture>("Logout");
}
}
When using an imported grammar, the ISpecContext
for the current specification is available on the original Fixture object, but you cannot depend on the SetUp()
and TearDown()
methods to be called on the original Fixture.