14
votes

I am trying to learn specflow and right now. Currently I have 2 feature files.

In the second feature file, I am reusing a step from the first feature file.

Specflow automatically recognizes the step from the first feature file and when specflow generated the steps for my second feature, it was smart and did not regenerated the step I am reusing.

But this step is a Given step and it initializes a member field of the feature class.

Without using scenario context, how can I reuse a step from another feature file that initialize a member of the class ?

Edit

For example, if you have a Given I am logged in that is used in several feature file. This "Given" creates a user object which is logged and store it as a member in .cs feature file.

When you use the same Given in another .feature, Specflow does not regenerate it in the correspond .cs file. When you debug the a scenario which is using it, it executes it from the first .cs file.

But I can't access the member of the first .cs feature file. I am planning to use a static member but perhaps there is another solution ?

Thanks a lot.

4
It would really help to see an example scenario from the first feature file, and one from the second that reuses the step. Depending on this, I'll probably answer suggesting how to write codebehind that shares that variable; or I may end up advising you that the re-used step really shouldn't be shared, and suggest alternativees.perfectionist
Also, I think you mean "step definition file" rather than "feature class".perfectionist
Just edited my question. Thanks.user2846391

4 Answers

20
votes

The big point here is that Step Bindings are global. This seems to be a common anti-pattern with Specflow that lots of people goes through. Initially you have a phase of creating hierarchies of binding classes that match your feature files. Instead you need to create collaborating classes that don't match the features but instead produce features through their collaboration.

It's just like your main application code. You wouldn't have a single ATMMachineCashWithdrawal class, instead you would have an ATMMachine, that has a PINCodeCheck, an OperationSelection and a WithdrawalOperation. Those objects collaborate to make your "I want to withdraw cash" feature, and when you add a "Check my balance" feature, you can reuse everything except the WithdrawalOperation.

The bindings in Specflow are the same. We might have an ATMTester that knows how to setup an ATMMachine and supplies your Given I have a cash machine full of cash and you could have a CustomerTester that knows how to fake/mock/setup your account balance with Given my account has loads of money in it.

Fortunately SpecFlow provides ways to collaborate the classes too. Have a look at http://www.specflow.org/documentation/Sharing-Data-between-Bindings/

10
votes

I just had the same issue. You need to set the attribute "Binding" for the derived classes and set the scope for each of them.

Let's say you have 2 Features:

  • Feature: My First Feature
  • Feature: My Second Feature

    Feature: My First Feature
    Background: 
    Given a precondition
    When ...
    Then ...
    
    Feature: My Second Feature
    Background: 
    Given a precondition
    When ...
    Then ...
    

and you have a BaseClass definining a shared behaviour

    // no Binding attribute for the BaseClass
    public class BaseClass
    {
        [Given(@"a precondition")]
        public void GivenAPrecondition()
        {
        }
    }

and then 2 classes defining the behaviour for the 2 features

[Binding]
[Scope(Feature = "My First Feature")]
public class MyFirstFeature : BaseClass
{

}

[Binding]
[Scope(Feature = "My Second Feature")]
public class MySecondFeature : BaseClass
{

}
0
votes

One thing I've done is used a single, massive partial class split up between various *.cs files.

This lets you keep relevant things separated in their own files, but still gives you plenty of options in re-using your fixture code.

e.g. (Feature1Steps.cs)

namespace YourProject.Specs
{
    [Binding] // This can only be used once.
    public partial class YourProjectSpecSteps
    {
        // Feature 1 methods ...
    }
}

And for the next feature (Feature2Steps.cs)

namespace YourProject.Specs
{
    public partial class YourProjectSpecSteps // same class, already bound
    {
        // Feature 2 methods ...
    }
}
0
votes

I know you mentioned you have two feature files but you might also want to think about creating one feature file with two scenarios where the scenarios use a common step as well as two identically named steps with different implementations. (overloaded functions)

Ex:  Login.featue file

Feature: Login
    Test the login functionality of the application.
    Will verify if the username and password combination is working as expected.

Scenario: Verify if the login functionality is working
    Given I have navigated to the application
    # user name and password are hard coded
    When  I fill in my form
    | username       | password     |
    |[email protected]    | pwd          |
    .... 

Scenario: Verify if the login functionality is working for sql server data
    Given I have navigated to the application
    # gets the user name and password from the database
    When  I fill in my form  
    ....

 LoginSteps.cs file

   [Binding]
   public  class LoginSteps
    {

        [Given(@"I have navigated to the application")]
        public void GivenIHaveNavigatedToTheApplication()
        {
         // this code is used by both scenarios
         Browser.Navigate().GoToUrl(ConfigurationManager.AppSettings["TestUrl"]);
        }

        // hard coded username and password
        [When(@"I fill in my form")]
        public void WhenIFillInMyForm(Table table)
        {
            dynamic credentials = table.CreateDynamicInstance();
            LoginFunction(credentials.username, credentials.password);
        }

        // gets the username and password from the database
        [When(@"I fill in my form")]
        public void WhenIFillInMyForm()
        {
            string username = "";
            string password = "";
            string sql = <select statement>;

            using (SqlConnection connection = new SqlConnection())
            {
                connection.ConnectionString = ConfigurationManager.ConnectionStrings["SQLProvider"].ConnectionString;
                connection.Open();

                SqlCommand myCommand = new SqlCommand(sql, connection);

                using (SqlDataReader myDataReader = myCommand.ExecuteReader())
                {
                    while (myDataReader.Read())
                    {
                        username = myDataReader["name"].ToString();
                        password = myDataReader["pwd"].ToString();
                    }
                }
            }
           LoginFunction(username, password);
         }