1

This question is more for general discussion on sharing values in SpecFlow. Please provide any constructive feedback you may have based on your experience in SpecFlow.

I am relatively new to this technology, and when looking for a solution to sharing values among step definition files, I found ScenarioContext.Current.Get and ScenarioContext.Current.Set. These are very handy, but as I see it, there are a couple of problems.

  1. There is quite a lot of typing involved in this approach.
  2. Value types are inserted and retrieved with string indexes, so string constants or enums need to be used to ensure consistency among step definitions.
  3. It might not be safe to assume that the value you're trying to retrieve has been inserted.

I have come up with an abstraction that I think makes some of this a bit easier to live with, and I'm wondering what people think about it.

Problem: has my value been set?

My solution to this was to wrap the ScenarioContext.Current in a singleton accessor class. This class behaves like ScenarioContext.Current, except that it throws an AssertInconclusiveException when the value can't be found.

    private static ScenarioContextAccessor instance;

    public static ScenarioContextAccessor Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new ScenarioContextAccessor();
            }

            return instance;
        }
    }

    private ScenarioContextAccessor() { }

    public T Retrieve<T>(string index)
    {
        try
        {
            T val = (T)ScenarioContext.Current[index];
            if (val == null)
            {
                throw new Exception();
            }

            return val;
        }
        catch
        {
            throw new AssertInconclusiveException(index + " of type " + typeof(T).Name + " was not found in the current scenario context. Did you execute your steps out of order?");
        }
    }

    public T Retrieve<T>()
    {
        try
        {
            T val = ScenarioContext.Current.Get<T>();
            if (val == null)
            {
                throw new Exception();
            }

            return val;
        }
        catch
        {
            throw new AssertInconclusiveException("No object of type " + typeof(T).Name+ " could be found in the current scenario context. Did you execute your steps out of order?");
        }
    }

    public void Set(string index, object value)
    {
        ScenarioContext.Current[index.ToLower(CultureInfo.InvariantCulture)] = value;
    }

    public void Set<T>(T value)
    {
        ScenarioContext.Current.Set<T>(value);
    }
}

Problem: This requires too much typing!

My solution to this is to have any step definition that requires these values to define them as private properties backed up by the ScenarioContextAccessor. Any property that accesses a value type uses a string constant as the index.

    private string FolderName
    {
        get
        {
            return ScenarioContextAccessor.Instance.Retrieve<string>(FolderingScenarioContextKey.FolderName);
        }
        set
        {
            ScenarioContextAccessor.Instance.Set(FolderingScenarioContextKey.FolderName, value);
        }
    }

    private UserDocumentMetadata Metadata
    {
        get
        {
            return ScenarioContextAccessor.Instance.Retrieve<UserDocumentMetadata>();
        }
        set
        {
            ScenarioContextAccessor.Instance.Set<UserDocumentMetadata>(value);
        }
    }

So now I can access my shared values as easily as if they were simple properties.

Please offer any constructive feedback you may have. Thanks!

Samo
  • 8,202
  • 13
  • 58
  • 95

2 Answers2

0

I think the feature you are looking for is Context Injection (dependency injection): https://github.com/techtalk/SpecFlow/wiki/Context-Injection

This lets you share classes between step definitions.

AndyM
  • 3,574
  • 6
  • 39
  • 45
0

As for part 1, I disagree. I find it more useful to have a test fail outright if data has not been set up correctly for it.

As for part 2, I already use a pattern like this, particularly for getting the instance of the object being tested as it saves lots of typing & potential error.

Another pattern useful (depending on circumstance) instead of your part 1 solution if you just need a dummy instance of a class is a lazy initialisation approach:

public static Mock<T> GetOrMockAndStore<T>() where T : class
    {
        Mock<T> output;
        if (ScenarioContext.Current.TryGetValue(out output))
        {
            return output;
        }
        else
        {
            output = new Mock<T>();
            ScenarioContext.Current.Set(output);
        }
        return card;
    }

I am using Moq - very useful framework.

perfectionist
  • 4,256
  • 1
  • 23
  • 34