How do I make a step argument depending on the configuration?

Problem

I am using SpecFlow to create a suite of integration tests for a REST service.

I am running the package in several different configurations. (I have multiple Build configurations, each with its own set of app.config transforms.)
In C # code, it is very easy to check the configuration and execute other code on it. I can just 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 for every execution of this step, but I don't want to parameterize the configuration-dependent portion of the step differently in every scenario. Is there a way to do this in a function file ? I would like to do something like this (pseudocode, this of course doesn't work):

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 differently in each 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 were implemented in the C # code for this step, this would not be possible.

Real world example

I would like to use this to integrate REST service testing. Let's say I am using basic authentication for which I need to set a header on my object RestClient

.
I have a helper step to set the auth header to a specific username and password.

The tricky part is that I have multiple build configurations (say Staging and Prod) for which I need different test credentials. Also, I am calling different APIs in different scripts of my function that also require different credentials.

So, with the above pseudo-syntax, this is what I would 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 condition the C # implementation under "Considering I have set the auth header ..." then I would not be able to specify different usernames for different scenarios.

+3


source to share


5 answers


You don't want configurable data to be missing in your file files at all. Instead, create a generic step whose definition reads a 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 on the system, consider using a scripting scheme 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 will become normal parameters for determining the step:

[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
}

      

+1


source


We do something similar for different environments, but we have an app.config for tests that have several "alternative" configurations for dev, qa, and uat, and we read the value of a named parameter from one of those 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 choose a config based on the attribute value isCurrent

, but you can just choose it for the name based on the environment variable.

Then your tests don't know the exact values ​​that are being used, but simply refer to ConfigParam1



based on your real world example I don't like the implementation details in the tests (what if you are using some other authentication mechanism) and change my specs 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 add a configuration 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 can then call a generic step to set the actual header values

+1


source


you could achieve what you want with a script outline and tagged examples, but then you only have to run 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 only run tests in specific categories (either Test

, or Production

)

if you are using nunit (or XUnit or any other test runner that defaults to line tests for scripting scripts) as your test runner, be aware of this question

+1


source


Your tests should always be the same - a test with an "if" in it is at least two tests. The correct way to deal with this problem is to isolate the system under test so that it accepts a parameter (or is otherwise supplied with a value) that represents the configuration value, and then records the tests for all applicable scenarios.

0


source


I would write a function:

Scenario: Test LoggingService
    Given I set the auth header with valid user and password
    When I call the LoggingService
    # ...

      

Set the file App.config

:

<appSettings>
    <add key="EnvironmentUserName" value="..."/>
    <add key="EnvironmentPassword" value="..."/>
    <!-- ... -->
</appSettings>

      

and follow step like:

public void GivenISetTheAuthHeader()
{
    string username = System.Configuration.ConfigurationManager.AppSettings["EnvironmentUserName"];
    string password = System.Configuration.ConfigurationManager.AppSettings["EnvironmentPassword"];
    // ...
 }

      

0


source







All Articles