Rik Hemsley
Rik Hemsley

Reputation: 887

Can SpecFlow features be shared between Steps?

We have code which we'd like to test from three different angles:

As we don't want to write feature files three times, it seems that we should share features between Steps, but I can't figure out how this might be done, aside from perhaps sharing files between VS projects, which has always seemed a bit, well, flaky.

Is sharing feature files between projects the best way to achieve this, or is there a more sensible method?

Upvotes: 4

Views: 3597

Answers (6)

Benjamin Baumann
Benjamin Baumann

Reputation: 4055

What worked for us, was something similar to Sam Holder 1st option.

  1. Reference both dependency in your VS project so that both dll will be copied in your output (compilation) directory.
  2. use different app.config files for your different environments.
  3. in every app.config configure the right stepassemblies (comment/delete the one not needed)

    <specFlow>
      <stepAssemblies>
        <stepAssembly assembly="Tests.API" />
        <!--stepAssembly assembly="Tests.UI" /-->
      </stepAssemblies>
    </specFlow>
    

I didn't need the manual Assembly.Load call because declaring a stepAssembly will pass the reference to NUnit as a bindingAssembly (see here).

If I have both assemblies declared as stepAssemblies, I run into an "ambiguous reference" exception.

Hope this helps because sharing features is very helpful, if not necessary (when you upgrade your software and keep your functional scenario tests for example).

Upvotes: 1

Sam Holder
Sam Holder

Reputation: 32936

I know this was asked quite a while ago, but what I think you want is possible, and there are several ways to achieve it as I understand. In effect what you really want is to define the feature once but switch out the steps that are called for the feature based on whether you are calling the Internal service, the public webservice or the WebApp. There is a discussion of the various approaches to solve this on the mailing list for specflow but the gist of it is as follows (example show an API vs UI approach but the same applies to your Internal vs web service vs webapp I believe):

Option 1 #

(thanks to Oliver Friedrich from the mailing list)

you need to create one assembly for each of the options you want to vary for the steps, so one for internal, one for web service and one for webapp. You tell specflow about all of the different assemblies in the config, like so:

<specFlow>
  <stepAssemblies>
    <stepAssembly assembly="Tests.API" />
    <stepAssembly assembly="Tests.UI" />
  </stepAssemblies>
</specFlow>

then in your common test steps you have some [BeforeTestRun] step which chooses which assembly to load the steps from:

[Binding]
public class TestRunSetup {

    // this method needs to be static
    [BeforeTestRun]
    public static void BeforeTestRun() 
    {
        if (RunApiTests()) // <-- implement this method to choose whether to run the tests via your chosen method 
        {
            Assembly.Load("Tests.API");
        }
        else 
        {
            Assembly.Load("Tests.UI");
        }
    }
}

Option 2 ##

(thanks to Gáspár Nagy from the mailing list)

Try generating the tests during the build. I'm not sure exactly how this will work but it is an area we can investigate.

Option 3 ##

(thanks to Dan Mork from the mailing list)

If you use SpecFlow's dependency injection capabilities then you can create an interface for executing the calls you want to do and use this in a common set of steps. Then you can have 3 implementations of that interface, one which calls your internal service, one which calls the web service and one which manipulates the web app. All that then remains is to inject the correct implementation of that interface into the specflow step files, which can be done like this:

// the abstract concept of the system that could be implemented with 
Selenium, HttpClient, etc. 
public interface IDocument 
{ 
    string Title { get;} 
    void Load(); 
} 

// the steps that are executed when the scenarios from your feature 
file are executed 
[Binding] 
public class Steps 
{ 
    private readonly IDocument _document; 

    public Steps(IDocument document) 
    { 
        _document = document; 
    } 

    [Given("something")] 
    public void GivenSomething() 
    { 
        // set up given state 
    } 

    [When("I view the document")] 
    public void WhenIViewTheDocument() 
    { 
        _document.Load(); 
    } 

    [Then(@"the title should be ""(.*)""")] 
    public void Then(string title) 
    { 
        Assert.ArEqual(_document.Title, title); 
    } 
} 

// this is where the magic happens - get the dependency injection 
// container and register an IDocument implementation
[Binding] 
public class Dependencies 
{ 
    private readonly IObjectContainer _objectContainer; 

    public Dependencies(IObjectContainer objectContainer) 
    { 
        _objectContainer = objectContainer; 
    } 

    [BeforeScenario] 
    public void RegisterDocumentInterfaces() 
    { 
        // register the correct IDocument implementation - UI or API 
    } 
} 

That still leaves you with the problem of how to know which implementation to register. That will depend on the specifics of your solution, your build environment, your test execution environment, etc. Some options for that...

  • tag parameter to the BeforeScenarioAttribute
  • create two different build configurations for your project that each define different constants and use precompiler directives to build the right registrations into the code
  • add conditional logic to check an environment variable
  • add conditional logic to check a configuration setting
  • I haven't checked, but perhaps ScopeAttribute is extensible for you to create a subclass and provide your own logic for whether or not a BeforeScenarioAttribute method is executed.

Hopefully this will give you some options to do what you want.

IMHO the first option is probably the best, though option 3 is pretty sweet as well.

Upvotes: 6

fgperianez
fgperianez

Reputation: 111

We had a similar case where the same features had to work for different input methods. Our solution was similar to option #3 from Sam Holder's answer, but we wanted to use configuration files instead of code to decide which implementation to use in each case.

This could be achieved by doing our own DI mechanism. Besides being able to register implementations on runtime, it also reads a configuration file when it is first called and loads the described classes.

Now for each project which shares features with another, we have a .config file where we state the correct implementation for each shared interface (input data ones mostly).

The limitation with this approach is that you can only configure classes with parameterless constructors, but for us it has not been a problem yet.

If you are interested in the solution I will upload it to github.

Upvotes: 2

Ryan Burnham
Ryan Burnham

Reputation: 2669

Steps are not bound to a feature, if the steps are the same or at least some of them as long as the wording is the same or you add an extra Attribute to your step definition method then they can be reused in other scenario's or features.

Depending on the case i would either write these as extra Scenario's or Features.

Upvotes: 0

Justin Pihony
Justin Pihony

Reputation: 67065

I am not sure that I am thinking of what you want correctly or not, but this might be what you are looking for:

Scenario Outline: Multiple approaches to test same code
    Given I am using <ApproachToCallCode>
    When I do something
    Then I expect this result

Scenarios: Approaches
    |ApproachToCallCode|
    |Internal          |
    |WebService        |
    |WebApp            |

Then, you could use a conditional in the given method? I am not sure if this is the best approach, but it should work.

Upvotes: 0

Kevin Holditch
Kevin Holditch

Reputation: 5303

I think that these are really 3 different tests so should have 3 sets of feature files in my mind. However if you really dont want to go down this route then you could do something like specify an examples table e.g.

Scenario Outline: Testing app

Given I have performed a call to my service using <application>
When I do something
Then this happens

Examples:
|Application|
| web service |
| web application |
| direct call |

The above scenario would run 3 times, passing in the 3 values. The value would be passed into the given method (in this case) so you could use that to setup some context and then reuse the same step definition files. Each step would then know if it was meant to use the web service, web app or direct call.

I would still have reservations about doing this as they are separate tests but it's a way to do what you want to achieve.

Upvotes: 0

Related Questions