On the bases of feedback and issues, a lot of SpecFlow users seem to try to put step definitions and other binding codes to base classes. I’ll try to explain why inheritance doesn’t fit to the concept of global step definitions used by SpecFlow.
Why would somebody use base classes for step definitions?
Well, I don’t really know the answer, but I think they try to build up a maintainable automation code base by separating common tasks to a shared place (e.g. “login” that is used in many places in the specs). Also sometimes people want to use base classes to access some kind of shared state among step definitions.
There is nothing wrong with these goals, but base classing and inheritance are not a good solution for this in SpecFlow (or in other tools in the Cucumber tool family). Why?
Step definitions and other bindings are global in SpecFlow. This means that regardless of the way you split the step definition methods into multiple classes, finally they will be put to a global registry. Whenever SpecFlow executes a step, it tries to find the one and only step definition matching to the step text. If there are multiple matching step definitions, it throws an exception (“Ambiguous step definitions found”). When building the registry, SpecFlow only considers classes that are decorated with the “[Binding]” attribute.
Let’s take the following example: we have three classes, a base class (“A”) and two subclasses, “B” and “C” each has a step definition matching for “foo”, “bar” and “boz” respectively.
And let’s take the following scenario:
If both classes “B” and “C” have a “[Binding]” attribute, the “foo” step will be added to the registry twice, once as part of class “B” and once as part of class “C”, so the execution will be ambiguous (with a strange message, like “Ambiguous step definitions found for step ‘Given foo’: A.GivenFoo(), A.GivenFoo()”).
It is even worse if you have a “[Binding]” attribute on the base class as well, as in that case the “foo” step will also be registered from the base class.
As said, it is completely fine to separate the step definitions to multiple classes, but you should just make them as separate classes. As step definitions are global, you will be able to access the “common” step definitions from any scenarios, anyway.
Scoping
SpecFlow has a dangerous feature that allows you to scope step definitions to different contexts (tags, features or scenarios). Our goal with scoping was to be able to automate scenarios within a project in different ways (e.g. some tests are automated through UI, others through the controller layer). If scoping is used, then base classes can somehow make more sense, but still not perfect.
Let’s imagine that class “B” contains the UI automation steps, class “C” the controller automation steps, and class “A” the ones that are independent from the layer (e.g. setup the database with some data).
Putting a [Scope] attribute to classes “B” and “C” will also implicitly scope the “foo” step inherited from the base class, so the test will pass and is not ambiguous. But what is the benefit of using a base class for “foo” instead of a standalone class? I’m pretty sure, people prefer the base class, because that way, the special steps (“bar” or “boz”) can share state with the “foo” through instance fields. For example, the database setup step can also save the inserted records into an in-memory list, so that other automation steps can use it.
This way of state sharing looks practical at the first glimpse, but actually you are building a tight dependency between a set of automation steps and a single common step class. If the number of common steps grows and you would like to split it to multiple smaller classes, you will not be able to do it, because the custom automation classes can have only one base class (multiple inheritance is not supported in .NET).
For sharing state across multiple step definition classes, context injection provides a much better solution, which does not suffer from the inheritance issues.
Conclusion
Step definitions are global in SpecFlow granting you the possibility to split larger step definition classes to smaller ones. Because of the global nature, you don’t need to use any base class to implement “common” step definitions. For sharing state among step definition classes, context injection can be used, which is type safe and does not enforce class inheritance restrictions.
Developing with SpecFlow Course in London, June 22-14; Vienna July 20-22, 2015
Integration testing, Gherkin scenario writing and SpecFlow usage are the topics of my upcoming course in London, June 22-14; Vienna July 20-22, 2015. If you would like to learn more about these topics or further dates. See the SpecFlow course page. 10% discount is provided from the list price, if you mention code BLOG763.
One thought on “SpecFlow tips: Problems with placing step definitions to base classes”