22

I want to have the following test step class structure:

[Binding]
public class BaseStep
{
    [Given(@"there is a customer")]
    public void GivenThereIsACustomer(Table table)
    {
        HandleCustomer(table);
    }

    protected virtual void HandleCustomer(Table table)
    {
    }
}

[Binding]
public class FeatureOneStep : BaseStep
{
    protected override void HandleCustomer(Table table)
    {
         // feature one action
    }

    [Given(@"feature one specific step")]
    public void GivenFeatureOneSpecificAction(Table table)
    {
        // do something
    }

}

[Binding]
public class FeatureTwoStep : BaseStep
{
    protected override void HandleCustomer(Table table)
    {
         // feature two action
    }

    [Given(@"feature two specific step")]
    public void GivenFeatureTwoSpecificAction(Table table)
    {
        // do something
    }
}

"Given there is a customer" is a common step that is used in both FeatureOne and FeatureTwo, but it will have different handling logic inside the two features. So I decide to put this step definition into a base class and override the protected methods in two derived classes respectively.

However, when I ran the tests, I have the following error:

TechTalk.SpecFlow.BindingException: Ambiguous step definitions found for step
'Given there is a customer': 
CustomerTestBase.GivenThereIsACustomer(Table),   
CustomerTestBase.GivenThereIsACustomer(Table)

Can any one tell me how to fix this issue?

wd113
  • 477
  • 1
  • 9
  • 18

4 Answers4

32

Just figuring this out now myself, so a couple of notes (hopefully somebody can use this in the future):

  • Don't include the [Binding] attribute on the base class
  • Create a derived class for each feature file
    • Add the [Binding] attribute to the derived class (will automatically include all step definitions in the base class)
    • Add a [Scope] attribute to the derived class; specify the name of the feature for the named parameter Feature
RunOfTheShipe
  • 431
  • 5
  • 9
  • 10
    This is the right answer. I understand that SpecFlow doesn't want us using inheritance, but it seems to me to be an unnecessarily confusing design choice. Object oriented programming techniques already provides powerful methods of code reuse. Why reinvent the wheel? – Dan Mar 31 '16 at 14:48
13

The answer is simple; Don't use inheritance to define your bindings.

At run time SpecFlow finds its methods to call by scanning globally across all public classes looking for methods with matching [Given] attributes. This means that you can't have two different implementations for the same Given there is a customer statement, which if you think about it is quite a sensible design decision that will reduce ambiguity.

AlSki
  • 6,868
  • 1
  • 26
  • 39
  • He's right. Inheritance only works if you control the initialization. Since the test is auto-generated, you can't define which concrete class will be used. – Brantley Blanchard Aug 30 '14 at 20:58
  • You could write a step that does the construction and then calls the subclass if you're stuck on using inheritance. 1 - BaseClass bc = FeatureOneClass(); and 2 - bc.featureonestep; – Brantley Blanchard Aug 30 '14 at 21:01
  • If the base class is abstract, I believe this reasoning does not apply. SpecFlow _should_ only ever be interested in concrete classes, but it would seem that it scans too loosely. – Timo Feb 11 '20 at 13:32
11

This worked well for me :

public class BaseSteps
{
    [Given(@"Method called")]
    public virtual void WhenMethodCalled()
    {

    }
}    



[Binding]
[Scope(Feature= "specific_feature")
public class DerivedSteps : BaseSteps
{
    [Given(@"Method called")]
    [Scope(Feature= "specific_feature", Tag ="specific_tag")]
    public override void WhenMethodCalled()
    {

    }
}
0

Specflow is a tool for handling the connection between the .feature file and the .cs file. This is not a tool to handle our abstractions. If we need any kind of abstractions or structure to prevent duplication or any other design problem, we can create it separately.

If you consider Specflow as a tool for binding, I am sure that you won't look for a way to handle your abstraction using it.

For example, define an abstract CustomerService

public abstract class CustomerService
{
   public abstract void Handle();
}
Mehrdad
  • 1,523
  • 9
  • 23