One of the things I commonly see people struggling with is how to “push” environment specific settings into the execution of a SpecFlow scenario.
This post will provide some background into the problem and later present my solution to the problem.
Sometimes you might have a scenario (or several) that you wish to execute on several different environments (commonly dev|test|prod) where the urls, identifiers, connection strings or whatever are different.
Have a look at this scenario as an example:
note: Even though this scenario might not be suitable at all for SpecFlow it still serves as an illustration of the problem.
Let’s imagine that we would like to run that test on:
- the local machine (debug)
- on the test environment
- on the production environment
We would need to tell the execution context (the step definitions) what the urls to the “servers” are for the environment at hand.
And how do we commonly accomplish that?
One example I’ve seen is using a special scenario to configure the environment, something like this (with a couple of variations):
Setting up a scenario like this just for the sake of configuration is not the way to go. Besides, I wouldn’t consider “Given I’m using the xxx environment” a valid cucumber given step.
Copy/Paste Scenarios with Tags
Another example that’s pretty common is having the same scenario twice in one feature with the only thing distinguishing them being a tag, something like this:
The biggest one is that it’s not DRY and the only reason for the tagging is to configure the execution context which might not be the best way of organization.
app.config (with SlowCheetah)
A third options I’ve seen is using the app.config file, something like this:
That and maybe a combination together with SlowCheetah (XML Transforms) to handle the the multiple environments (as configurations) is a step in the right direction…however…
First of all, you have now hardcoded your setting towards AppSettings which is always bad (easily solved though, through an interface).
Secondly, SlowCheetah’s xml transformations requires that you re-compile your code to get a “new” app.config file for your environment. This might lead you compile your specs in as many configurations as you have environments, again…not ideally.
Alternative: Configurable Settings
As I mentioned earlier, using app.settings is a step in the right direction. I’m going to use that concept and elaborate on it.
First, I’ll move all my environment settings into an interface.
By moving all of our environment settings (urls, identifiers, etc.) to an external service (illustrated by an interface as seen above) and having different implementations we can have hardcoded settings and configurable settings.
The main idea here is to return the hardcoded/local settings when the code compiles in debug on my box (so to say).
However, when the code compiles in release mode (as it would on a build server) the other, configurable, implementation is used.
My implementation of the environment settings is configurable since it reads the actual values from an external xml file thus the C# compiled code doesn’t know about the values, just how to provide them.
That xml file only needs to exists in the same directory as the specs when “they” execute and hence the environment will be configurable just-in-time and not at compilation-time.
note: that external xml file might also be app.config if you think that’s easier…it’s up to you really
Next, I’ll show you how to wire this up with SpecFlow.
Using Context Injection
Since SpecFlow supports dependency injection OOB for scenarios, a.k.a. context injection, we can make use of that and safely rely on the fact that SpecFlow will inject an implementation of IEnvironmentSettings at runtime. However, we still need to take control over which implementation is injected.
Wiring it all up using a SpecFlow hook (and some magic)
Again I’m using a SpecFlow hook for configuration, like this:
|public class EnvironmentSettingsSupport|
|private static IObjectContainer m_Container;|
|public EnvironmentSettingsSupport(IObjectContainer container)|
|m_Container = container;|
|public void InitializeEnvironment()|
|// settings are hardcoded|
|var settings = new HardcodedSettings();|
|// enables the user to configure the settings via xml|
|var settings = new ConfigurableSettings();|
However, I’m actually configuring the container (IObjectContainer) and telling it which implementation to use (lines 15-28). When the code is compiled in release (when it leaves my box) the container will be instructed to use the configurable settings implementation as I showed earlier.
In order for this to work you have to define the “RELEASE” compilation symbol. (DEBUG is defined by default).
You do that either through the properties UI dialog:
Or through editing the project file: