I\'m evaluating SpecFlow and I\'m a bit stuck.
All samples I have found are basically with simple objects.
Project I\'m working on heavily relies on a complex ob
I go one step further when my Domain Object Model starts to get complex, and create "Test Models" that I specifically use in my SpecFlow scenarios. A Test Model should:
Let's take a Blog as an example.
Consider the following scenario written so that anyone familiar with how a Blog works knows what's going on:
Scenario: Creating a Blog Post
Given a Blog named "Testing with SpecFlow" exists
When I create a post in the "Testing with SpecFlow" Blog with the following attributes:
| Field | Value |
| Title | Complex Models |
| Body | This is not so hard.
|
| Status | Working Draft |
Then a post in the "Testing with SpecFlow" Blog should exist with the following attributes:
| Field | Value |
| Title | Complex Models |
| Body | This is not so hard.
|
| Status | Working Draft |
This models a complex relationship, where a Blog has many Blog Posts.
The Domain Model for this Blog application would be this:
public class Blog
{
public string Name { get; set; }
public string Description { get; set; }
public IList Posts { get; private set; }
public Blog()
{
Posts = new List();
}
}
public class BlogPost
{
public string Title { get; set; }
public string Body { get; set; }
public BlogPostStatus Status { get; set; }
public DateTime? PublishDate { get; set; }
public Blog Blog { get; private set; }
public BlogPost(Blog blog)
{
Blog = blog;
}
}
public enum BlogPostStatus
{
WorkingDraft = 0,
Published = 1,
Unpublished = 2,
Deleted = 3
}
Notice that our Scenario has a "Status" with a value of "Working Draft," but the BlogPostStatus enum has WorkingDraft. How do you translate that "natural language" status to an enum? Now enter the Test Model.
The BlogPostRow class is meant to do a few things:
Code:
class BlogPostRow
{
public string Title { get; set; }
public string Body { get; set; }
public DateTime? PublishDate { get; set; }
public string Status { get; set; }
public BlogPostRow()
{
}
public BlogPostRow(BlogPost post)
{
Title = post.Title;
Body = post.Body;
PublishDate = post.PublishDate;
Status = GetStatusText(post.Status);
}
public BlogPost CreateInstance(string blogName, IDbContext ctx)
{
Blog blog = ctx.Blogs.Where(b => b.Name == blogName).Single();
BlogPost post = new BlogPost(blog)
{
Title = Title,
Body = Body,
PublishDate = PublishDate,
Status = GetStatus(Status)
};
blog.Posts.Add(post);
return post;
}
private BlogPostStatus GetStatus(string statusText)
{
BlogPostStatus status;
foreach (string name in Enum.GetNames(typeof(BlogPostStatus)))
{
string enumName = name.Replace(" ", string.Empty);
if (Enum.TryParse(enumName, out status))
return status;
}
throw new ArgumentException("Unknown Blog Post Status Text: " + statusText);
}
private string GetStatusText(BlogPostStatus status)
{
switch (status)
{
case BlogPostStatus.WorkingDraft:
return "Working Draft";
default:
return status.ToString();
}
}
}
It is in the private GetStatus and GetStatusText where the human readable blog post status values are translated to Enums, and vice versa.
(Disclosure: I know an Enum is not the most complex case, but it is an easy-to-follow case)
The last piece of the puzzle is the step definitions.
Step:
Given a Blog named "Testing with SpecFlow" exists
Definition:
[Given(@"a Blog named ""(.*)"" exists")]
public void GivenABlogNamedExists(string blogName)
{
using (IDbContext ctx = new TestContext())
{
Blog blog = new Blog()
{
Name = blogName
};
ctx.Blogs.Add(blog);
ctx.SaveChanges();
}
}
Step:
When I create a post in the "Testing with SpecFlow" Blog with the following attributes:
| Field | Value |
| Title | Complex Models |
| Body | This is not so hard.
|
| Status | Working Draft |
Definition:
[When(@"I create a post in the ""(.*)"" Blog with the following attributes:")]
public void WhenICreateAPostInTheBlogWithTheFollowingAttributes(string blogName, Table table)
{
using (IDbContext ctx = new TestContext())
{
BlogPostRow row = table.CreateInstance();
BlogPost post = row.CreateInstance(blogName, ctx);
ctx.BlogPosts.Add(post);
ctx.SaveChanges();
}
}
Step:
Then a post in the "Testing with SpecFlow" Blog should exist with the following attributes:
| Field | Value |
| Title | Complex Models |
| Body | This is not so hard.
|
| Status | Working Draft |
Definition:
[Then(@"a post in the ""(.*)"" Blog should exist with the following attributes:")]
public void ThenAPostInTheBlogShouldExistWithTheFollowingAttributes(string blogName, Table table)
{
using (IDbContext ctx = new TestContext())
{
Blog blog = ctx.Blogs.Where(b => b.Name == blogName).Single();
foreach (BlogPost post in blog.Posts)
{
BlogPostRow actual = new BlogPostRow(post);
table.CompareToInstance(actual);
}
}
}
(TestContext - Some sort of persistent data store whose lifetime is the current scenario)
Taking a step back, the term "Model" has gotten more complex, and we've just introduced yet another kind of model. Let's see how they all play together:
You can almost think of a Test Model as a View Model for your SpecFlow tests, with the "view" being the Scenario written in Gherkin.