SpecFlow — a few tips-n-tricks

Jay Barden
Capgemini Microsoft Blog
8 min readFeb 5, 2020

As regular readers will know, I am a little obsessed with testing at all levels and, whilst the majority of the tests are Unit tests (please see What is Test-Driven Development and What is Test-Driven Development Part 2 to learn more on why that is — assuming you’re not already familiar with the Testing Pyramid), Acceptance tests are crucial to achieving a true, end-to-end, test suite — and SpecFlow is the perfect tool * to achieve this in the C# development world!

*Your opinion may, of course, vary!

Some excellent posts have been created including this one on the basics of using SpecFlow for Acceptance Tests, but in this blog, I want to delve a little deeper to help you avoid some of the more common mistakes made when initially using SpecFlow.

Due to the expected length of this post, I’ve split it into two parts — please keep an eye out for part two! Part 1 will focus on the various ways of sharing step definitions, whilst part 2 will focus on sharing data and making data consumption easier.

What is SpecFlow?

For a detailed description, please visit the SpecFlow website but, in summary, it is a great framework for implementing tests with the Gherkin language.

The SpecFlow website has a lot of incredibly useful information and Getting Started guides — in this article, I intend on high-lighting a few of the features that I’ve found extremely useful over the years.

All my current projects are written with .Net Core 2.x and utilise SpecFlow 3, but I’ve attempted to only cover the features that are not .Net Core / SpecFlow 3 specific in the hope that you can use these tips / tricks with older versions of SpecFlow too.

Sharing Steps

Code reuse is very important — and not just in production code. Thankfully, SpecFlow makes this easy via several different methods for sharing steps.

Common Steps

Possibly the most obvious way to share steps is by utilising a CommonStepDefinitions class:

The above is a traditional C# class and the name could be anything — the key factor is the [Binding] attribute.

  1. Our CommonStepDefinitions class doesn’t have to be given any specific name — the [Binding] attribute is enough to ensure the runner will find the class when it initialises.
  2. As with any class, we can perform set-up within the constructor — including utilising the SpecFlow Dependency Injection, for example, to obtain an instance of the ScenarioContext.
  3. As the [BeforeScenario] attribute states, this method will run before every scenario within the project. *
  4. As the [AfterScenario] attribute states, this method will run after every scenario within the project. *
  5. An example of a [Given] attribute that can be used by any scenario within the project. **

*This may be what you want but it may not. [Scope] attributes can be used to limit the scope but using them in the CommonStepDefinitions class would be counter-intuitive. Also, reverting to using [Scope] is considered an anti-pattern — more details are available on the SpecFlow website.

**Any step definition — Given, When, Then — can be added to the CommonStepDefinitions class, the example above includes a common step that I’ve added to nearly every SpecFlow project.

Use of attributes like [BeforeScenario] in the CommonStepDefinitions class should be given careful thought prior to implementing (and even more thought if using the [BeforeFeature] / [BeforeTestRun] attributes), but they can be extremely useful.

Multiple Attributes

Another way to share steps — either within the CommonStepDefinitions class (or any dedicated feature file for that matter) is to add multiple bindings to the method you want to share:

The above could equally be achieved by rewording one of the [Given] steps but context is king and, possibly, the full scenario makes more sense with the different terminology, but the steps to login and signin will almost certainly be the same. Multiple [Given] attributes allow us to be flexible in our scenario text as we need whilst reducing the number of steps we must maintain. The use of [Given] / [When] / [Then] on the same method is also technically possible — however, the context of the test itself may make this impractical.

Calling a Step from within a Step

Another option is to call an existing step from within a step:

Whilst the above compiles, it will lose some context that may be relevant during debugging of test failures in the future. SpecFlow has a preferred approach:

In the above, rather than call the method directly, we’re calling the actual step — one caveat to this approach is the calling class must derive from Steps

but this does give at least one not immediately obvious benefit: calling a method with this approach will, amongst others, mean that the test output will list the inner step in the history of the test run. Should an exception / error of some kind occur in the inner step, the history will reflect the real location and potentially save time debugging 😃

Now, if you’re looking at the magic string in the inner step call, yes, it does make the test brittle during refactoring as the step call must match 100% the actual declaration — “signed in” becoming “signed-in” would lead to an step not found error at run-time. With this small downside in mind, this is a good approach to sharing steps, but perhaps one best left to when you’re sure your step names won’t change.

Sharing Steps between Projects in a Solution

So far, we’ve covered a few useful ways to share steps within the same project but, if you’re working on a large solution or a solution that contains all of your tests for multiple UIs / APIs etc., you may find that the above only gets you so far. For example, the UI may be different but, chances are, the login steps are the same username, password and maybe remember me / Capture steps for each UI. SpecFlow has this covered via its support for External Projects.

To use a separate (External) project, there are a couple of set-up steps that must be performed:

  1. The common project must have the SpecFlow package installed.
  2. The consuming project must have the App.Config updated (or created if it does not exist) to include the stepAssemblies section as shown:

With this simple change in place, we can now consume steps from an external project — not bad for a few lines of configuration!

Hooks and Scoped Bindings

Hooks and Scoped Bindings can be used to share steps in a different way.

Hooks

  1. [BeforeTestRun]/[AfterTestRun]
  2. [BeforeFeature]/[AfterFeature]
  3. [BeforeScenario]/[AfterScenario]
  4. [BeforeScenarioBlock]/[AfterScenarioBlock]
  5. [BeforeStep]/[AfterStep]

Each of the above runs before / after the entire test run / feature / etc. The one that may not be obvious is the BeforeScenarioBlock / AfterScenarioBlock — this is akin to:

In the above, the Given and the When would be considered separate blocks (and the step marked with the BeforeScenarioBlock would run before both steps) but the Then and the and would be one block. Thus, the BeforeScenarioBlock would run just once, prior to the Then block. I’ve yet to find a scenario where I needed this functionality, but I’ve included as your mileage may vary.

As you can see, you can trigger additional code before / after almost anything.

Scoped Bindings

Scoped Bindings can prevent the ambiguous step error and can be set at the Feature, Scenario or Tag level. Below is a Feature-level example:

However, use of Scope should be considered an anti-pattern and only used sparingly. If the step has the same name, rarely is there a need for a different implementation — Scope exists for those rare times and should only be used if, for some contextual / business reason, you cannot reword the step definition to remove the ambiguity that way.

Combining Scope Filtering

Assuming we do need to use the Scope attribute to limit the use of a step, there are two ways we can achieve this — and or or.

To combine in an and way:

To combine in an or way:

The first example will only match if the Tag is some tag and the scenario is The specific scenario whereas the second example will match if the Tag is some tag or the scenario is The specific scenario. At first glance, this may not be so obvious so be careful. Also, some code analysers (such as StyleCop) may suggest separating the attributes — making the and become an or

Generally, none of the Scoped Binding approaches should be used — they exist for when you simply cannot change the wording in your GWT (Given…When…Then…) — for business reasons or good ol’ stubbornness.

Hook Tag Filtering

As we saw earlier, Hooks is another name for BeforeSomething / AfterSomething and, as a result, there may be times you need to limit their scope. Firstly, I’d like to repeat that this should be the exception and not the rule as, by design, they are expected to run BeforeSomething / AfterSomething. That said, Hooks can be filtered at:

  1. Feature level
  2. Scenario level
  3. Scenario block level
  4. Step level

For example, the below BeforeScenario will only run if the Scenario has The specific tag applied to it:

Directly combining Hook filters is not supported. The current approach is:

In the above, the scenarioContext has been injected into the constructor and assigned to a private member field.

I hope you’ve found this article on SpecFlow and the various ways to share steps/ limit the use of your steps useful. In the next article, I will share some tips-n-tricks on working with data in a strongly-typed way by leveraging the power of SpecFlow.

Happy Testing!

Interested in working with like-minded people?

Join the Capgemini Microsoft team, open roles here.

--

--