Handling test data clean up in SpecFlow

Writing advanced UI tests with SpecFlow and Selenium often requires some test data to be created. During the tests execution this test data is changed. One of requirements for a good test is repeatedness. Test have to be launched any times and should complete with the same results.

So we need some sort of clean-up procedures for our tests in order to satisfy above requirement. Let’s consider what SpecFlow could offer us in this case.

Hooks

Hook is the statement of code that is executed after or before particular event. Here are the most useful from my mind:

  1. [AfterScenario(“MyTag”)] – code is executed after all the scenarios marked with @MyTag.
  2. [BeforeScenario(“MyTag”)] – code is executed before all the scenarios marked with @MyTag

You could find more info about SpecFlow hooks on the project wiki here.

Let’s consider very simple scenario, where we need some sort of clean-up procedures:

Scenario: User changes company
Given: User is work for a company ABC
When: User submits to work for the company DFE
Then: User is now working for the DFE

What we could expect when test is finished? That the user is now bound to the DFE company. But what happened when we try to repeat this test. Sure it will fail as now user is not bound to the ABC, but to DFE. What we need is to organise cleanup action after test execution, so user will be bound to the ABC again.

How could we achieve such behaviour. Well using the hooks in such way.

[AfterScenario]
public void AfterScenario()
{
    var title = ScenarioContext.Current.ScenarioInfo.Title;
    if (title == "User changes company")
    {
        //do some actions to rebound user to the initial company
    }
    if (title == "SomeOtherTest")
    {
        //do some clean-up for that
    }
}

Usually logic inside if statement might be quite difficult, as it requires some action to do and reuse the our Page Objects. So AfterScenario method become bloated and hard to read. This is not how we want to work.

This is when declarative programming comes in game. So let’s replace above statement with 2 separate methods, market with our custom attribute;

[ScenarioCleanup("User changes company")]
public void ResetBindedUser()
{
    //do some actions to rebound user to the initial company
}

[ScenarioCleanup("SomeOtherTest")]
public void CleanupAfterOtherTest()
{
    //do some other actions
}

Code for newly created attribute;

public class ScenarioCleanup : Attribute
{
    public string ScenarioTitle { get; set; }

    public ScenarioCleanup(string scenarioTitle)
    {
        ScenarioTitle = scenarioTitle;
    }
}

And finally the code where all parts are gathered;

[Binding]
public class TestBehaviour
{
    public void BeforeTestRun()
    {
        _teardownActions = BuildDictionary(this.GetType());
    }

    [AfterScenario()]
    public void AfterScenario()
    {
        _commonTestBehaviour.ResolveScenarioCleanup(ScenarioContext.Current.ScenarioInfo.Title, this);
        //some other code, like disposing selenium driver or so
    }

    [ScenarioCleanup("User changes company")]
    public void ResetBindedUser()
    {
        //do some actions to rebound user to the initial company
    }

    public void ResolveScenarioCleanup(string title, object instance)
    {
        MethodInfo cleanupMethod;
        if (_teardownActions.TryGetValue(title, out cleanupMethod))
        {
            cleanupMethod.Invoke(instance, null);
        }
    }

    private Dictionary<string, MethodInfo> _teardownActions;

    private Dictionary<string, MethodInfo> BuildDictionary(Type type)
    {
        var withAttrs = type.GetMethods()
            .Where(m => m.GetCustomAttributes(typeof(ScenarioCleanup), false).Length > 0)
            .ToDictionary(key => ((ScenarioCleanup[])key.GetCustomAttributes(typeof(ScenarioCleanup), false))[0].ScenarioTitle, val => val);
        return withAttrs;
    }
}

The code is pretty straightforward. Before TestRun via reflection we gather up all the cleanup methods inside. When scenario is executed, wi hit inside AfterScenario() method, where we look inside cleanup actions dictionary in order to know if we need to execute something. If yes, then call that method via reflection;

If we make such cleanups, what then should be inside the Given step? In my opinion in Given steps, there should be only Assertion about the data, not changing the data; All changes should inside When block;
In our scenario: Given: User is work for a company ABC
Inside a code bounded to this step, should be something like that;

[Given("User is work for a company (.*)")]
public void GivenUserWorksForCompany(string companyName)
{
    //navigate to user page
    var user = GetUser();
    if (!user.BoundTo(companyName))
    {
        throw new DataIntegrityException("User is not bound to the company " + companyName);
    }
}
This entry was posted in Selenium, SpecFlow and tagged , , . Bookmark the permalink.

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s