Reputation: 163
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 ?
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.
Upvotes: 16
Views: 22707
Reputation: 19
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);
}
Upvotes: -1
Reputation: 696
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 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
{
}
Upvotes: 13
Reputation: 6961
The big point here is that Step Binding
s 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/
Upvotes: 22
Reputation: 434
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 ...
}
}
Upvotes: 2