Reputation: 8043
I am using SpecFlow to create an integration test suite for a REST service.
I am running the suite in multiple different configurations. (I have multiple Build configurations, each with its own set of app.config transformations.)
In the C# code, it's very simple to check the configuration and execute different code based on it. I can simply do something like this.
[Given(@"I set the test parameter to ""(.*)""")]
public void GivenISetTheTestParameter(string parameter)
{
if(CurrentConfiguration == Config.Test)
this.testParameter = parameter + "Test";
else if(CurrentConfiguration == Config.Prod)
this.testParameter = parameter + "Prod";
}
The problem with this approach is that it works the same way for every execution of this step, but I wan't to parameterize the configuration-dependent part of the step differently in every scenario.
Is there any way to do this in the feature file? I would like to do something like this (pseudo-code, this is not working of course):
If (CurrentConfiguration == Config.Test)
Given I set the test parameter to "ParameterTest"
Else If (CurrentConfiguration == Config.Prod)
Given I set the test parameter to "ParameterProd"
Then I can use this parameterization in a different way in every scenario:
Scenario: Test 1
If (CurrentConfiguration == Config.Test)
Given I set the test parameter to "ParameterTest1"
Else If (CurrentConfiguration == Config.Prod)
Given I set the test parameter to "ParameterProd1"
...
Scenario: Test 2
If (CurrentConfiguration == Config.Test)
Given I set the test parameter to "ParameterTest2"
Else If (CurrentConfiguration == Config.Prod)
Given I set the test parameter to "ParameterProd2"
...
If the condition was implemented in the C# code for the step, this wouldn't be possible.
I would like to use this for integration testing a REST service. Let's say I use basic authentication, for which I need to set a header on my RestClient
object.
I have a helper step for setting the auth header to a specific user name and password.
The tricky part is that I have multiple build configurations (let's say Staging and Prod), for which I need different test credentials. Also, I'm calling different APIs in the different scenarios of my feature, which also need different credentials.
So with the above introduced pseudo-syntax, this is what I'd like to do:
Scenario: Test LoggingService
If (CurrentConfiguration == Config.Test)
Given I set the auth header for the user "logging_test_user" and password "p4ssword"
Else If (CurrentConfiguration == Config.Prod)
Given I set the auth header for the user "logging_prod_user" and password "p4ssword"
...
When I call the LoggingService
...
Scenario: Test PaymentService
If (CurrentConfiguration == Config.Test)
Given I set the auth header for the user "payment_test_user" and password "p4ssword"
Else If (CurrentConfiguration == Config.Prod)
Given I set the auth header for the user "payment_prod_user" and password "p4ssword"
...
When I call the PaymentService
...
If I can only put the condition into the C# implementation of the "Given I set the auth header..." step, then I wouldn't be able to specify different user names for the different scenarios.
Upvotes: 3
Views: 1224
Reputation: 32946
We do something similar for different environments, but we have a app.config for the tests which has several 'alternate' configurations for dev, qa and uat and we read the value of a named parameter from one of these sections.
We have something like this
<testingEnvironments>
<testingEnvironment name="Dev" messageUrl="https://somedevurl/" isCurrent="true">
<ConfigParam1>SomeValue</ConfigParam1>
</testingEnvironment>
<testingEnvironment name="QA" messageUrl="https://somedqaurl/" isCurrent="false">
<ConfigParam1>SomeValueInQA</ConfigParam1>
</testingEnvironment>
<testingEnvironment name="UAT" messageUrl="https://someuaturl/" isCurrent="false">
<ConfigParam1>SomeValueUAT</ConfigParam1>
</testingEnvironment>
</testingEnvironments>
we select the config based on the value of the isCurrent
attribute, but you could just select it on the name based on an environment variable.
Then your tests are ignorant of the exact values used then just refer to ConfigParam1
based on your real world example I don't like the implementation detail in the tests (what if you use some other authentication mechanism) and would restructure my specifications like this:
Scenario: Test LoggingService
Given I am the logging service default user for the current environment
When I call the LoggingService
...
Scenario: Test payment Service
Given I am the payment service default user for the current environment
When I call the PaymentService
...
and would add config something like this:
<testingEnvironment name="DEV" messageUrl="https://somedevurl/" isCurrent="false">
<userCredentials>
<LoggingService>
<defaultUser name="logging_test_user" password="p4ssword" />
</LoggingService>
<PaymentService>
<defaultUser name="payment_test_user" password="p4ssword" />
</PaymentService>
</userCredentials>
</testingEnvironment>
<testingEnvironment name="UAT" messageUrl="https://someuaturl/" isCurrent="false">
<userCredentials>
<LoggingService>
<defaultUser name="logging_prod_user" password="p4ssword" />
</LoggingService>
<PaymentService>
<defaultUser name="payment_prod_user" password="p4ssword" />
</PaymentService>
</userCredentials>
</testingEnvironment>
your individual steps could then call a common step to set the actual header values
Upvotes: 1
Reputation: 32946
you could achieve what you want with a scenario outline and tagged examples, but then you would have to run only some tests in some environments:
Scenario Outline: Testing the LoggingService
Given I set the auth header for user "<Username>" and password "<Password>"
@Production
Examples:
| Username | Password |
| logging_prod_user | p4ssword |
@Test
Examples:
| Username | Password |
| logging_test_user | p4assword |
then configure your test runner to run only tests in certain categories (either Test
or Production
)
if you are using nunit (or XUnit or any other test runner that defaults to using row tests for running scenario outlines) as your test runner, be aware of this issue
Upvotes: 1
Reputation: 2793
I would write the feature:
Scenario: Test LoggingService
Given I set the auth header with valid user and password
When I call the LoggingService
# ...
Set the App.config
file:
<appSettings>
<add key="EnvironmentUserName" value="..."/>
<add key="EnvironmentPassword" value="..."/>
<!-- ... -->
</appSettings>
and implement the step as:
public void GivenISetTheAuthHeader()
{
string username = System.Configuration.ConfigurationManager.AppSettings["EnvironmentUserName"];
string password = System.Configuration.ConfigurationManager.AppSettings["EnvironmentPassword"];
// ...
}
Upvotes: 0
Reputation: 59037
Your tests should always be the same -- a test with an "if" in it is at least two tests. The proper way to tackle this is to isolate the system under test such that it takes a parameter (or is otherwise provided with a value) that represents the configuration value, then write tests for all of the applicable scenarios.
Upvotes: 0
Reputation: 18858
You don't want the configurable data in your feature files at all. Instead, create a generic step whose definition reads the config file:
Scenario: Test LoggingService
Given I set the auth header
And in C#:
[Given(@"I set the auth header")]
public void GivenISetTheAuthHeader()
{
string username = System.Configuration.ConfigurationManager.AppSettings["RestServiceUserName"];
string password = System.Configuration.ConfigurationManager.AppSettings["RestServicePassword"];
}
And in App.config:
<appSettings>
<add key="RestServiceUserName" value="..."/>
<add key="RestServicePassword" value="..."/>
If different usernames have different permissions in the system, then consider using a Scenario Outline instead:
Scenario Outline: Testing the LoggingService
Given I set the auth header for user "<Username>" and password "<Password>"
Examples:
| Username | Password |
| user1 | pass1 |
| user2 | pass2 |
And they become normal parameters to your step definition:
[Given("I set the auth header for user """(.*)""" and password """(.*)"""")]
public void GivenISetTheAuthHeaderForUserAndPassword(string username, string password)
{
// set the user and password on the auth header
}
Upvotes: 2