15

TL;DR; How can I create a specflow test that calls another test as its first step?

Given I already have one specflow test
And I want to run another test that goes deeper than the first test  
Then I create a second test that runs the first test as its first step
And I add additional steps to test the deeper functionality

Sorry, little bit of specflow humor there.

eg I have a test that creates a sale already:

Given I want to create a sales order
And I open the sales order page
And I click the add new order button
Then a new sales order is created

And I want to have another test that tests adding a sales line

And another test that tests completing the sale

And another test that cancels the sale

And .. so on

All of those tests would start with the same first four steps as the simple test, which breaks the DRY principle. So how can I do it so that the first step of the 2nd test just runs the first test? eg something like:

Given I have run the create sales order test  // right here it just runs the first test
And I add a sales order line
Then the order total is updated

If every test starts off with the same first four lines, and later on I realize that I need to change the simple create sale test, then I will also need to go and find and fix everywhere else that repeats those four lines.

EDIT: Note that this should also be able to work across features. eg The simple test above is defined in the sales feature. But I would also have a credits feature, and that would require creating a sale each time in order to be able to credit it:

Given I want to credit a sale
And I run the create sales order test
And I complete the the sale
And I click the credit button
Then the sale is credited
JK.
  • 21,477
  • 35
  • 135
  • 214
  • Is creating your `Given I have run the create sales order test` step which executes the three previous `Given` steps methods not working? Or you want an other way? I doubt you want to repeat the `Assert` part in your following tests. – Pierre-Luc Pineault Mar 17 '15 at 22:07
  • I don't mind that it will have to repeat the asserts that will exist in the first test. I do want to be able to run it without having to write the same 4 steps into lots of different tests. – JK. Mar 17 '15 at 22:14
  • yes, but why don't you create the new `Given` like your 'something like` part, which calls the three previous `Given` manually? Also fits the scenario in your edit since you can reuse your `I have run the create sales order test` step where you want. You were 99% there in your question. – Pierre-Luc Pineault Mar 17 '15 at 22:17
  • @Pierre-LucPineault I think the OP is not aware of that as a possibility and that is what he is asking for – Sam Holder Mar 17 '15 at 22:18

4 Answers4

18

As noted already you can use a background for this (and that's probably the best option in most situations), but you can also create a step which calls the other steps.

[Binding]
public class MySteps: Steps //Inheriting this base class is vital or the methods used below won't be available
{
    [Given("I have created an order")]
    public void CreateOrder()
    {
         Given("I want to create a sales order");
         Given("I open the sales order page");
         Given("I click the add new order button");
         Then("a new sales order is created");
    }
}

which you can then use in your scenario:

Scenario: I add another sale
    Given I have created an order
    When I add a sales order line
    Then the order total is updated

This has the advantage that this composite step can be used anywhere in the scenario and not just as a starting point. This step can then be reused across multiple features if you need

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • Thanks, that's pretty close actually. However I do still want the 1st test to have the steps actually written out in the feature file, so there will still be some duplication (but just 2x instead of n times). – JK. Mar 18 '15 at 00:28
  • That's fine. You can use the steps if you want or you can group them together with a 'composite'step of you need. It gives the best of both works – Sam Holder Mar 18 '15 at 06:20
  • A couple of things to be wary of: 1. you need to use the correct Given(...) When(...) Then(...) or And(...) to match your step definition and 2. if you use an "And" after the step then it will "inherit" whichever you last used in code, instead of what you last used in the feature file. – Steve Jul 18 '16 at 14:17
  • That bug should be fixed in the next release @steve – Sam Holder Jul 18 '16 at 14:34
  • @SamHolder I thought it was a feature :) – Steve Jul 18 '16 at 17:02
  • 1
    I only see a couple of problems with this, 1) if you change the base scenario you must change the step definition `CreateOrder` to match which is prone to user error 2) it is difficult to parameterize data used by the base scenario. Is there any way to autogenerate the step definition for `I have created an order` so it matches the definition of the scenario? – MikeW Dec 07 '16 at 09:58
  • 1
    This is not a good option. First, it links the method call dynamically at runtime, so if you update the step definition and forget about this place, then it won't even fail, it will just try to call the method and silently skip it, if it cannot find the matching method. This is very annoying, when you have to debug. – HamsterWithPitchfork Jun 01 '18 at 08:08
6

Use a Background:

Background:
    Given I want to create a sales order
    And I open the sales order page
    And I click the add new order button
    Then a new sales order is created

Scenario: I add another sale
    When I add a sales order line
    Then the order total is updated

Scenario: I add cancel a sale
    When I cancel a sale
    Then the order total is updated to 0

etc.
RagtimeWilly
  • 5,265
  • 3
  • 25
  • 41
  • That looks good, but would only work within a single feature, right? Is there any way to re-use the test across multiple features? I'll update the question. – JK. Mar 17 '15 at 22:15
  • You can use `[BeforeTestRun]` or `[BeforeScenario]` but it will apply to every scenario then. I'm not sure if that's what you want? – RagtimeWilly Mar 17 '15 at 23:15
  • No it would only be for specific related tests. Tests that start the same way but then go on to test something further. – JK. Mar 18 '15 at 00:30
4

You don't need to run actual steps to create a sales order. Just implement a step definition that does this for you as a one-liner.

First, the fictional SalesOrder class:

public class SalesOrder
{
    public double Amount { get; set; }
    public string Description { get; set; }
}

Then the step definitions

using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Assist;

[Binding]
public class SalesOrderSteps
{
    [Given("I have already created a Sales Order")]
    public void GivenIHaveAlreadyCreatedASalesOrder()
    {
        var order = new SalesOrder()
        {
            // .. set default properties
        };

        // Save to scenario context so subsequent steps can access it
        ScenarioContext.Current.Set<SalesOrder>(order);

        using (var db = new DatabaseContext())
        {
            db.SalesOrders.Add(order);
            db.SaveChanges();
        }
    }

    [Given("I have already created a Sales Order with the following attributes:")]
    public void GivenIHaveAlreadyCreatedASalesOrderWithTheFollowingAttributes(Table table)
    {
        var order = table.CreateInstance<SalesOrder>();

        // Save to scenario context so subsequent steps can access it
        ScenarioContext.Current.Set<SalesOrder>(order);

        using (var db = new DatabaseContext())
        {
            db.SalesOrders.Add(order);
            db.SaveChanges();
        }
    }
}

Now you can create Sales orders as a one-liner and optionally include some custom attributes:

Scenario: Something
    Given I have already created a Sales Order

Scenario: Something else
    Given I have already created a Sales Order with the following attributes:
        | Field       | Value             |
        | Amount      | 25.99             |
        | Description | Just a test order |

If you need to access that SalesOrder object in other step definitions without querying for it in the database, use ScenarioContext.Current.Get<SalesOrder>() to retrieve that object from the scenario context.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
-1

If I understand the question correctly, you want to call other scenarios across different feature files.

  1. You can handle this by creating a step which would call the steps in the scenario (basically nested steps like the accepted answer above).
  2. Add the created step to the Background

or

  1. Create a function which would call the steps in the scenario.
  2. Add a tag @create_sale_order to the scenarios that needs a sale order as precondition.
  3. Implement a before scenario hook for the tag @create_sale_order and call the function created at step 1.
Navin
  • 273
  • 3
  • 15