Specflow: How to use strong type collection in step definitions instead of Table

Specflow is an excellent tool to write acceptance tests for your applications. When we write a specification we provide some test data to run against  the spec. Sometimes we need to provide the test data in table format as below.

specflow-figure-1

As you see from the above feature file snippets that book collection used in two different steps. By default specflow uses Table object as parameter for your step definitions as below. Take a look at Step Definitions below:

[Given(@"I have following books available")]
public void GivenIHaveFollowingBooksAvailable(Table table)
{
    var books = table.Rows.Select(row => new Book
    {
        Id = Convert.ToInt32(row["id"]),
        Title = row["title"],
        Price = Convert.ToDecimal(row["price"])
    });

    foreach (var book in books)
    {
        _repository.Insert(book);
    }
}

[When(@"I search books by title ""(.*)""")]
public void WhenISearchBookByTitle(string title)
{
    var result = _repository.Search(title);

    ScenarioContext.Current.Set(result,"search-result");
}

[Then(@"I should get following books")]
public void ThenIShouldGetFollowingBooks(Table table)
{
    var expectedBooks = table.Rows.Select(row => new Book
    {
        Id = Convert.ToInt32(row["id"]),
        Title = row["title"],
        Price = Convert.ToDecimal(row["price"])
    }).ToList();

    var searchResult = ScenarioContext.Current.Get<IEnumerable<Book>>("search-result");

    foreach (var book in searchResult)
    {
        Assert.True(expectedBooks.Contains(book));
    }
}

The above code illustrates that specflow pass the table data as Table object in your steps and then you need to convert that Table to collection of domain or DTO object. Now you most probably thinking I have duplicate code here. Yes that’s correct. So we can improve our code by using a helper class to convert Table to books or for simple object scenario you can use table.CreateSet() extension method.

But specflow provides an elegant solution that allow your to pass collection of domain/dto in your step methods. “StepArgumentTransformation” attribute is your solution. To do this first your need to tell specflow how to convert a Table to collection of books.

[Binding]
public class Transforms
{
    [StepArgumentTransformation]
    public IEnumerable<Book> TableToBooksTransform(Table table)
    {
        return table.Rows.Select(row => new Book
        {
            Id = Convert.ToInt32(row["id"]),
            Title = row["title"],
            Price = Convert.ToDecimal(row["price"])
        });
    }
}

As specflow now knows how to convert Table to collection of books so we can refactor our step definitions as below:

[Given(@"I have following books available")]
public void GivenIHaveFollowingBooksAvailable(IEnumerable<Book> books)
{
    foreach (var book in books)
    {
        _repository.Insert(book);
    }
}


[When(@"I search books by title ""(.*)""")]
public void WhenISearchBookByTitle(string title)
{
    var result = _repository.Search(title);

    ScenarioContext.Current.Set(result,"search-result");
}

[Then(@"I should get following books")]
public void ThenIShouldGetFollowingBooks(IEnumerable<Book> expectedBooks)
{
    var searchResult = ScenarioContext.Current.Get<IEnumerable<Book>>("search-result");

    foreach (var book in searchResult)
    {
        Assert.True(expectedBooks.Contains(book));
    }
}

Now all my step methods directly using collection of Books instead of Table. So i don’t need to convert Table to books anymore in my step methods and my code now looks much cleaner. Hope this will help you to make your test code cleaner.

Advertisements

One thought on “Specflow: How to use strong type collection in step definitions instead of Table

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s