I would like to add the same line to the start of each one of my SpecFlow tests.
This line specifies a list of several scenarios which will change over time, and therefore it is not feasible to maintain this list for every test.
For example:
Given I have set my site theme to <MyTheme>
|Theme Names|
|Theme 1 |
|Theme 2 |
|Theme 3 |
|Theme 4 |
|Theme 5 |
I'd like to have this test repeat for each of the themes. The list of themes is not set in stone, and should be maintained in a single place.
So far, I've successfully managed to create a Generator Plugin
, and I was planning on using this plugin to alter the SpecFlow feature immediately before the Test Class is generated. However, I can't see a way to edit the scenario from within this context.
Is it possible to get and set the scenario text from within an implementation of IUnitTestGeneratorProvider
?
I'm not set on using this method, so if anyone can suggest a better way to do this then I'd accept that too.
Apologies if I've gotten some terminology wrong- I've only just started using SpecFlow.
I'm adding this section to provide clarification as to what I'm actually after.
Suppose I had a test suite containing 800 tests. We have a business requirement to run each of those 800 tests on each of our available themes. The list of available themes can change at any time, and it would be unfeasible to maintain this list in more than a single location.
So, for example, if I had the following two tests:
Example A:
Given I set context to < site >
Given I go to base url
When I type <username> in username field
When I type <password> in password field
When I click login button
Examples:
| site | username | password |
| MySuperSite | chris | mypassword |
| MySuperSite2 | chris2 | mypassword |
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
I could simply manually change these tests to something along the lines of:
Example B:
Given I am using the < theme > theme
Given I set context to < site >
Given I go to base url
When I type <username> in username field
When I type <password> in password field
When I click login button
Examples:
| site | username | password | theme |
| MySuperSite | chris | mypassword | theme1 |
| MySuperSite2 | chris2 | mypassword | theme1 |
| MySuperSite | chris | mypassword | theme2 |
| MySuperSite2 | chris2 | mypassword | theme2 |
| MySuperSite | chris | mypassword | theme3 |
| MySuperSite2 | chris2 | mypassword | theme3 |
Given I am using the < theme > theme
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
Examples:
| theme |
| theme1 |
| theme2 |
| theme3 |
There are a few issues with this:
theme2
then someone will have to go through each test and remove it from the examples table (not too bad in the example above, but imaging we had >800 tests)Objective:
I'd like to be able to have our testers write tests in the style of Example A
, but have the tests themselves compile down to how they would if they were written in the style of Example B
.
I've created a generator plugin for specflow, with a view of intercepting the test creation and then programmatically adding the line Given I am using the < theme > theme
, and then update or add any example data as required. However, I can't seem to be able to do this from here.
Can anyone tell me if this is possible, and if so, how should I go about it?
OK, I figured this out. It took a few steps:
After looking through the source code on GitHub, I found UnitTestFeatureGenerator
which appears to be the class responsible for turning the specflow files into unit tests.
I then created a new class that inherited from UnitTestFeatureGenerator
and hides the GenerateUnitTestFixture
method from the base class.
In the body of my GenerateUnitTestFixture
class, I then add the extra steps required into the scenario before handing off of base.GenerateUnitTestFixture
to generate the unit tests. Here's the gist of it:
public class MultiThemeUnitTestFeatureGenerator : UnitTestFeatureGenerator, IFeatureGenerator
{
public MultiThemeUnitTestFeatureGenerator(IUnitTestGeneratorProvider testGeneratorProvider, CodeDomHelper codeDomHelper, GeneratorConfiguration generatorConfiguration, IDecoratorRegistry decoratorRegistry)
: base(testGeneratorProvider, codeDomHelper, generatorConfiguration, decoratorRegistry)
{}
public new CodeNamespace GenerateUnitTestFixture(Feature feature, string testClassName, string targetNamespace)
{
foreach (var scenario in feature.Scenarios)
{
scenario.Steps.Insert(0, new Given {Text = "Given I have <Theme> set as my current theme"});
//add any other steps you need....
}
return base.GenerateUnitTestFixture(feature, testClassName, targetNamespace);
}
}
Once I had all of this set up, I needed a way to tell specflow to use my new class instead of the currently registered UnitTestFeatureGenerator
. This was the complicated bit to get working as the documentation pretty much just said "Coming soon". Thankfully, I found an excellent blog post which outlines all the pitfalls.
My IGeneratorPlugin
implementation looks like this:
public class MultiThemeGeneratorPlugin : IGeneratorPlugin
{
public void RegisterDependencies(ObjectContainer container)
{}
public void RegisterCustomizations(ObjectContainer container, SpecFlowProjectConfiguration generatorConfiguration)
{
container.RegisterTypeAs<MultiThemeFeatureGeneratorProvider, IFeatureGeneratorProvider>("default");
}
public void RegisterConfigurationDefaults(SpecFlowProjectConfiguration specFlowConfiguration)
{}
}
Note That I register IFeatureGeneratorProvider
as opposed to IFeatureGenerator
. I had to create an implementation of IFeatureGeneratorProvider
that returns an instance of the implementation of IFeatureGenerator
that I was interested in:
public class MultiThemeFeatureGeneratorProvider : IFeatureGeneratorProvider
{
private readonly ObjectContainer _container;
public MultiThemeFeatureGeneratorProvider(ObjectContainer container)
{
_container = container;
}
public int Priority
{
get { return int.MaxValue; }
}
public bool CanGenerate(Feature feature)
{
return true;
}
public IFeatureGenerator CreateGenerator(Feature feature)
{
return _container.Resolve<MultiThemeUnitTestFeatureGenerator>();
}
}
I can't think of a simple way to do this, but my approach would probably be to exclude the theme from the tests but have the build server run the tests in an environment where the theme is already set up (by an environment variable or via some config file or similar) and then run the entire suite n times, once for each theme.
So I would have the scenarios look like this
Given I am using the currently configured theme
Given I am logged in
Given I go to base url
When I click logout button
Then I am logged out
where the first step reads the theme from the config file or environment variable and sets up the tests to use that theme.
Once the build server has run these tests, it would change the config/environment variable to the next theme and the run the suite again.
This gives you the ability to specify the themes in a single place (on the build server, or in a file that the BS uses to discover the themes) and have the tests run using each of those themes. It also might give you some performance advantage as you could parallelize the tests on the build server as each one would be a different test step and each test step could be run independently and on different machines.
For the devs you would only be able to test one theme at once and would have to manually alter the config/env variable to change to a different theme, which might not be ideal.
Someone else might have a better idea of how to do this in the way you are envisioning, fingers crossed
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With