Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Global [BeforeScenario], [AfterScenario] steps in SpecFlow

Tags:

specflow

We're trying to implement global hooks on our specflow tests and are not entirely sure how [BeforeScenario] and [AfterScenario] attributed methods work.

The way I've seen it done, those attributes are always defined in a class containing specific steps used in a few scenarios.

Can they go somewhere so they apply to all scenarios? Or does attributing the methods with [BeforeScenario] and [AfterScenario] cause them to be run for all scenarios, regardless of where they're actually placed?

like image 656
larryq Avatar asked Oct 28 '13 18:10

larryq


2 Answers

Hmm... From what I knew and according to the documentation these hooks are always global, i.e. from http://www.specflow.org/documentation/hooks/

Hooks

The hooks (event bindings) can be used to perform additional automation logic on specific events, like before the execution of a scenario.

The hooks are global but can be restricted to run only for features or scenarios with a specific tag (see below). The execution order of hooks for the same event is undefined.

In fact by producing a small demo project with the following

[Binding]
public class Unrelated
{
  [BeforeScenario]
  public void WillBeCalledIfGlobal()
  {
    Console.WriteLine("I'm global");
  }
}

[Binding]
public class JustTheTest
{
  [Given("nothing")]
  public void GivenNothing()
  {
     // Don't do anything
  }
}

Then the test specification of

As a developer
In order to understand how BeforeSpecifcation works
I want to know what the following does

Scenario: See if BeforeSpecifcation hook gets called
Given nothing

The get the output

I'm global
Given nothing
-> done: JustTheTest.GivenNothing() (0.0s)

So it really does look as if the documentation is correct, and you should use tagging to control if the BeforeScenario \ AfterScenario are run before or after your scenario.

There is also a good example of how tagging works here -> Feature-scoped step definitions with SpecFlow?

like image 148
AlSki Avatar answered Nov 06 '22 21:11

AlSki


Yes, you can create global BeforeScenario and AfterScenario methods, but in practice I find that this is not desirable, as usually the same before and after steps do not apply to all steps in a test project.

Instead I create a base class for my step definitions, which would have the BeforeScenario and AfterScenarios methods I'd like applied to all of my scenarios e.g.

public class BaseStepDefinitions
{
    [BeforeScenario]
    public void BeforeScenario()
    {
        // BeforeScenario code
    }

    [AfterScenario]
    public void AfterScenario()
    {
        // AfterScenario code
    }
}

Note that I have not used the Binding attribute on this class. If you do include it then the BeforeScenario and AfterScenario steps would be global.

I then derive my step definion classes from this base step definition class, so that they will have the Before and After scenario methods e.g.

[Binding]
public class SpecFlowFeature1Steps : BaseStepDefinitions
{
    [Given(@"I have entered (.*) into the calculator")]
    public void GivenIHaveEnteredIntoTheCalculator(int inputValue)
    {
        ScenarioContext.Current.Pending();
    }

    [When(@"I press add")]
    public void WhenIPressAdd()
    {
        ScenarioContext.Current.Pending();
    }

    [Then(@"the result should be (.*) on the screen")]
    public void ThenTheResultShouldBeOnTheScreen(int expectedResult)
    {
        ScenarioContext.Current.Pending();
    }
}

Whilst this approach is not global, by making all StepDefinitions derive from a BaseStepDefinition class we achieve the same outcome.

It also gives more control i.e. if you don't want the BeforeScenario or AfterScenario binding then don't derive from the base steps.


Sorry this doesn't work. As soon as you start using multiple Binding classes you end up with multiple calls. For example if I extend the example above to split the bindings into three classes,

[Binding]
public class SpecFlowFeature1Steps : BaseStepDefinitions
{
    [Given(@"I have entered (.*) into the calculator")]
    public void GivenIHaveEnteredIntoTheCalculator(int inputValue)
    {
        //ScenarioContext.Current.Pending();
    }
}

[Binding]
public class SpecFlowFeature2Steps : BaseStepDefinitions
{
    [When(@"I press add")]
    public void WhenIPressAdd()
    {
        //ScenarioContext.Current.Pending();
    }
}

[Binding]
public class SpecFlowFeature3Steps : BaseStepDefinitions
{
    [Then(@"the result should be (.*) on the screen")]
    public void ThenTheResultShouldBeOnTheScreen(int expectedResult)
    {
        //ScenarioContext.Current.Pending();
    }
}

public class BaseStepDefinitions
{
    [BeforeScenario]
    public void BeforeScenario()
    {
        // BeforeScenario code
        Console.WriteLine("Before. [Called from "+ this.GetType().Name+"]");
    }

    [AfterScenario]
    public void AfterScenario()
    {
        // AfterScenario code
        Console.WriteLine("After. [Called from " + this.GetType().Name + "]");
    }
}

Then when I run it, the output is

Before. [Called from SpecFlowFeature1Steps]
Before. [Called from SpecFlowFeature2Steps]
Before. [Called from SpecFlowFeature3Steps]
Given I have entered 50 into the calculator
-> done: SpecFlowFeature1Steps.GivenIHaveEnteredIntoTheCalculator(50) (0.0s)
And I have entered 70 into the calculator
-> done: SpecFlowFeature1Steps.GivenIHaveEnteredIntoTheCalculator(70) (0.0s)
When I press add
-> done: SpecFlowFeature2Steps.WhenIPressAdd() (0.0s)
Then the result should be 120 on the screen
-> done: SpecFlowFeature3Steps.ThenTheResultShouldBeOnTheScreen(120) (0.0s)
After. [Called from SpecFlowFeature1Steps]
After. [Called from SpecFlowFeature2Steps]
After. [Called from SpecFlowFeature3Steps]
like image 39
Ben Smith Avatar answered Nov 06 '22 21:11

Ben Smith