In SpecFlow how can I share data between steps/features?
Asked Answered
O

6

41

I have 2 features that use a common 'When' step but have different 'Then' steps in different classes.

How do I access, for example, the ActionResult from my MVC controller call in the When step in my two Then steps?

Overstuff answered 21/5, 2010 at 11:42 Comment(0)
C
38

In SpecFlow 1.3 there are three methods:

  1. static members
  2. ScenarioContext
  3. ContextInjection

Comments:

  1. static members are very pragmatic and in this case not so evil as we as developers might first think (there is no threading or need for mocking/replacing in step-definitions)

  2. See answer from @Si Keep in this thread

  3. If the constructor of a step definition class needs arguments, Specflow tries to inject these arguments. This can be used to inject the same context into several step-definitions.

    See an example here: https://docs.specflow.org/projects/specflow/en/latest/Bindings/Context-Injection.html

Cohen answered 3/6, 2010 at 6:12 Comment(4)
i think instance variables can be used as well, as in one of their examples: github.com/techtalk/SpecFlow-Examples/blob/master/BowlingKata/…Rainy
@Carl: Instance variables can be used for sharing data between stepdefinitions that are implemented in the same class. But the question was about stepimplementations in different classes.Cohen
The advantage ScenarioContext has over static members is that the state can then be shared with other test classes, so the in .feature files can be freely edited. This page explains the three methods reasonably well: blog.markvincze.com/how-to-store-state-during-specflow-testsPhelps
Just looked at an example using static and it grew to be a large mess so that is still an issue.Gospel
O
36

Use the ScenarioContext class which is a dictionary that is common to all the steps.

ScenarioContext.Current.Add("ActionResult", actionResult);
var actionResult = (ActionResult) ScenarioContext.Current["ActionResult"];
Overstuff answered 21/5, 2010 at 11:43 Comment(3)
Why say ye that it is horrible Colonel?Promotion
Simon has done the correct implementation for the question. The op can now refactor as required, rather than Simon try and guess how he wants it. mcintyre321 has made a nice helper method below.Rentschler
Note that ScenarioContext.Current is now obsolete - check out my answer.Improbity
F
16

I have a helper class which lets me write

Current<Page>.Value = pageObject;

which is a wrapper over the ScenarioContext. It works off the type name, so it would need to be extended a bit if you need to access two variables of the same type

 public static class Current<T> where T : class
 {
     internal static T Value 
     {
         get { 
               return ScenarioContext.Current.ContainsKey(typeof(T).FullName)
               ? ScenarioContext.Current[typeof(T).FullName] as T : null;
             }
         set { ScenarioContext.Current[typeof(T).FullName] = value; }
     }
 }

2019 edit: I would use @JoeT's answer nowadays, it looks like you get the same benefits without needing to define an extension

Fanelli answered 20/12, 2012 at 11:54 Comment(0)
M
12

I did not like using Scenario.Context because of the need for casting out each dictionary entry. I found another way to store and retrieve the item without the needing to cast it. However there is a trade off here because you are effectively using the type as the key access the object from the ScenarioContext dictionary. This means only one item of that type can be stored.

TestPage testPageIn = new TestPage(_driver);
ScenarioContext.Current.Set<TestPage>(testPageIn);
var testPageOut = ScenarioContext.Current.Get<TestPage>();
Malaya answered 6/7, 2015 at 11:54 Comment(2)
You could also make the Set more succinct e.g. ScenarioContext.Current.Set(testPageIn);Verbal
I kind of like having the code show that the type is the 'key' to store and retrieve the object. If we store the object without specifying the type then it's less obvious what key is is needed for retrieval. However determining that type should be pretty easy from looking at the surrounding code. It might require a trivial amount of extra mental effort determine the type of that object to write the line that retrieves it vs. seeing that type spelled out already in the line that stored it.Malaya
I
2

Since this is the first result that came up for me on Google, I just thought I'd mention that @jbandi's answer is the most complete. However, as of version 3.0 or later:

With SpecFlow 3.0, we marked ScenarioContext.Current and FeatureContext.Current as obsolete, to make clear that you that you should avoid using these properties in future. The reason for moving away from these properties is that they do not work when running scenarios in parallel.

(ScenarioContext and FeatureContext in SpecFlow 3.0 and Later)

Therefore, the most up to date way to store data during your test is using Context Injection. I would add example code but really the example code in the link is excellent so check it out.

You can imitate the now obsolete ScenarioContext.Current by injecting an instance into your bindings classes

[Binding]
public class MyStepDefs
{
 private readonly ScenarioContext _scenarioContext;

  public MyStepDefs(ScenarioContext scenarioContext) 
  { 
    _scenarioContext= scenarioContext ;
  }

  public SomeMethod()
  {
    _scenarioContext.Add("key", "value");

    var someObjectInstance = new SomeObject();
    _scenarioContext.Set<SomeObject>(someObjectInstance);
  
    _scenarioContext.Get<SomeObject>();
            
    // etc.
  }
}
Improbity answered 14/10, 2020 at 4:41 Comment(0)
F
0

You can define a parameter in your steps that is the key to the value you are storing. This way you can reference it in your later steps by using the key.

...
Then I remember the ticket number '<MyKey>'
....
When I type my ticket number '<MyKey>' into the search box
Then I should see my ticket number '<MyKey>' in the results 

You could store the actual value in a dictionary or property bag or similar.

Forefend answered 15/1, 2018 at 5:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.