Foreword
Some terms before we started.
Specification (AKA Spec) - describes a useful product feature which is supplied by several Scenarios
Context - is a System Under Test (SUT) state. Action Steps in general are actions to manipulate and validate Context
Main Part
A couple of days ago there was a private talking in the kitchen in the office.
We were discussing our approach of writing specifications.
Usually it comes with one solid class:
The problems with it are:
- Specs class is too huge and often became unmanagable
- We can't extract action steps into different classes as they refer to class instance fields so we can't reuse actions steps across specs
After some discussion we refactored it to the following model:
You see: our specs from now are absolutely independent on where Action Steps either Context located! That's a great benefit!
After a small refactoring in we've got 3 clearly separated classes: Specification, Action Steps and Context.
See some code below. Full examples can be found in US#33279, Plugins/Tp.Integration.Plugin.PopEmailIntegration folder
Specification
[TestFixture]
public class EmailProcessingSagaSpecs
{
[Test]
public void ShouldSkipAttachmentToDeletedProject()
{
@"Given project 1
And requester with email 'sender@company.com'
And sender email is 'sender@company.com'
And profile has rules:
|Rule |
|then attach to project 2 |
|then attach to project 1 |
When the email arrived
Then the message should be attached to project 1"
.Execute(In.Context<EmailProcessingSagaActionSteps>());
}
Action Steps
[ActionSteps]
public class EmailProcessingSagaActionSteps
{
[BeforeScenario]
public void BeforeScenarioInit()
{
ObjectFactory.Initialize(x => { });
}
#region Action Steps
private static EmailProcessingSagaContext Context
{
get { return ObjectFactory.GetInstance<EmailProcessingSagaContext>(); }
}
[Given("project $projectId")]
public void AddProject(int projectId)
{
var projectStorage = Context.Storage.Get<ProjectDTO>();
projectStorage.Add(new ProjectDTO {ProjectID = projectId});
}
Context
public class EmailProcessingSagaContext : PopEmailIntegrationContext
{
private readonly EmailMessage _emailMessage;
private readonly IList<AttachmentDTO> _attachments = new List<AttachmentDTO>();
public EmailProcessingSagaContext()
{
//Self-register Context in object factory in order to ensure single context instance during specs execution
ObjectFactory.Configure(x => x.For<EmailProcessingSagaContext>().Use(this));
_emailMessage = new EmailMessage {FromDisplayName = "John Smith"};
}
public EmailMessage EmailMessage
{
get { return _emailMessage; }
}
public IList<AttachmentDTO> Attachments
{
get { return _attachments; }
}