SpecFlow.Assist.Dynamic–how to use it

· November 1, 2011

This is the second post about <a href=”http://www.blogger.com/www.specflow.org”target=”_blank”>SpecFlow</a>.Assist.Dynamic – a little tool I wrote to help you write less code in your step definitions, and focus on the actual step instead of infrastructure. You can read the first post here – it explains little about what SpecFlow.Assist.Dynamic is.

In this post I’ll show you how some ways I use the dynamic features to and some tricks that you might not know about.

Again – this is not the documentation for SpecFlow.Assist.Dynamic – that can be found here.

Installation

With the power of NuGet it’s super-easy to install SpecFlow.Assist.Dynamic by simply go:

Install-Package SpecFlow.Assist.Dynamic

(Sidenote: Ha! It’s good to try out your own stuff. Found a bug in the 0.2 version on NuGet. Fixed it with the 0.2.1 version )

That will also pull down the latest version of SpecFlow and other dependencies needed.

Test data management

I found out that the most maintainable way to write scenarios is to have them initialize their own test data. Or at least on the feature level.

I’ve tried to do it by restoring back ups but that has two drawbacks.

First – it takes too long. And test that takes too long are not run.

Secondly – the features (specifications?) are not easy to follow. You have to know what test data is being read back. It’s hidden for most users. So why do we still try this approach? Laziness I guess… It’s just to much code to write.

Adding test data – single instances

There are two main features where SpecFlow.Assist.Dynamic helps you; adding test data in the Given-steps and comparing the outcome to the test data in you Then-steps.

For example for this Given step:

   1: Scenario: Searching for books
   2:     Given the following books in the system
   3:         | Title         | Author            | ISBN       |
   4:         | The Goal      | Eliah Goldratt    | 123345678  |
   5:         | Stick         | Ted and Dan Heath | 9955663322 |
   6:         | The Good book | God et. al        | 1          |

I can write the following short statements with SpecFlow.Assist.Dynamic:

   1: [Binding]
   2: public class DemoSteps
   3: {
   4:     [Given(@"the following books in the system")]
   5:     public void GivenTheFollowingBooksInTheSystem(Table table)
   6:     {
   7:         IList<dynamic> books = table.CreateDynamicSet().ToList();
   8:         foreach (var book in books) { DBHelper.AddBook(book); }
   9:     }
  10: }
  11: 
  12: public class DBHelper
  13: {
  14:     public static void AddBook(dynamic book)
  15:     {
  16:         var insertStatement = string.Format(
  17:             "INSERT INTO BOOKS (Title, Author, ISBN) VALUES ('{0}', '{1}', '{2}')",
  18:             book.Title, book.Author, book.ISBNNumber);
  19: 

  20:         // Call into the db...
  21:     }
  22: }

There’s a lot to be said about the data access code, for example you probably are using an ORM of sorts and hopefully you can send dynamic objects to it. I’ll show other ways to do that later.

Let’s focus on the SpecFlow.Assist.Dynamic instead. It’s not much to it – I simply create a list of dynamic objects and use my DBHelper to insert each one into the database. Shorten it up with Step Argument Transformations

You can use a trick that I’ve put into SpecFlow.Assist.Dynamic to shorten the code even further.

There is a SpecFlow feature called Step Argument Transformation that I use to shorten up code like the one above. With step argument transformation you can create small conversion methods that is triggered when certain types is used.

I have supplied three such argument transformations that converts Table arguments into:

  • A dynamic instance
  • A IEnumerable<dynamic>
  • A IList<dynamic>

So the example above can be written like this, using these step argument transformations:

   1: [Given(@"the following books in the system")]
   2: public void GivenTheFollowingBooksInTheSystem(IList<dynamic> books)
   3: {
   4:     foreach (var book in books) { DBHelper.AddBook(book); }
   5: }

Pretty slick, huh? This is how it works:

  • SpecFlow matches my Given statement with the Step definition marked with the Given-attribute above.
  • From the .feature-file it knows that a table is sent to the step. It send that table.
  • In the SpecFlow.Assist.Dynamic.dll step argument methods are found that matches a Table and the IList<dynamic> that I am using in the method signature
  • SpecFlow converts my argument using that method and simply returns a IList<dynamic> containing the data from the table.

You can read more about this on my wiki.

Oh that’s right – very important! To get this to work you need to add a configuration into your app.config:

1: <specFlow>
2:   <stepAssemblies>
3:     <stepAssembly assembly="SpecFlow.Assist.Dynamic" />
4:   </stepAssemblies>
5: </specFlow>

This make sure that the step arguments are picked up from the dll. Asserting results in the Then-steps

The other place where SpecFlow.Assist.Dynamic shines is when you are asserting that certain result of an action has happened.

If we expand my example from before in to a fully fledged feature, we get this:

1:Scenario: Searching for books
2:     Given the following books in the system
3:         | Title         | Author            | ISBN Number |
4:         | The Goal      | Eliah Goldratt    | 123345678   |
5:         | Stick         | Ted and Dan Heath | 9955663322  |
6:         | The Good book | God et. al        | 1           |
7:     When I search for Titles containing 'Goal'
8:     Then the results should show the following books
9:         | Title         | Author            | ISBN Number |
10:        | The Goal      | Eliah Goldratt    | 123345678   |

I can now write the following steps:

   1: [Binding]
   2: public class DemoSteps
   3: {
   4:     private IList<dynamic> _books;
   5:     private IEnumerable<dynamic> _filteredBooks;
   6: 
   7:     [Given(@"the following books in the system")]
   8:     public void GivenTheFollowingBooksInTheSystem(IList<dynamic> books)
   9:     {
  10:         foreach (var book in books) { DBHelper.AddBook(book); }
  11:         _books = books;
  12:     }
  13: 
  14:     [When(@"I search for Titles containing '(.*)'")]
  15:     public void PerformTitleSearch( string value)
  16:     {
  17:         _filteredBooks = from b in _books
  18:                          where b.Title.Contains(value)
  19:                          select b;
  20:     }
  21: 
  22:     [Then(@"the results should show the following books")]
  23:     public void ResultsContainsBooks(Table table)
  24:     {
  25:         table.CompareToDynamicSet(_filteredBooks.ToList());
  26:     }
  27: }

Don’t worry about the Given and When-steps, it’s just fake. I have save the original list into a private variable called _books and then simply filter that out in the When-step. In a real example the When-step would probably hit a webpage or a controller that in turn reads the database. And the Given-step would put the right data into the database before that.

But the interesting (and short!) stuff is the one liner in the Then-step. Here I can again use the power of SpecFlow.Assist.Dynamic and simply compare the sent-in table to the result of my When-action.

And before you start to think about it. This way of writing it is easier than to use the step argument transformations and compare two IList` to each other. That will be a lot more code.

What about single instances?

I have now only showed you the use for Sets. There’s similar methods for creating instances. Typically they are used to fill out forms or find a certain record in the database.

I leave the usage of those methods as a creative exercise for the reader. Let me know if you did anything that helped you.

Conclusion

I have shown you how I use these helpers. Please note that they are inspired to a great deal by the Table helpers in the SpecFlow.Assist namespace that have similar methods but for statically typed objects (that you need to create ^^)

I hope you find this helpful. Please let me know if I can improve.

In the next post I’ll show a final thing that marries this with Simple.Data. See you then.

Twitter, Facebook