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.
- There is quite a lot of typing involved in this approach.
- Value types are inserted and retrieved with string indexes, so string constants or enums need to be used to ensure consistency among step definitions.
- 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!