Gherkin variables - veepee-oss/gingerspec GitHub Wiki

Overview

GingerSpecs allows the use of variables directly in the feature file. This brings a lot of flexibility to the plain Gherkin syntax and transforms it into a very simple programming language. These variables are most commonly used to store the result of a previous step to use it afterward or pass variables in the maven command to allow a more parametrized execution of the tests. In the background, GingerSpec uses StringSubstitutor library to perform the variable replacement.

Syntax

Variables are expressed using the ${} symbol as the variable placeholder, so for example

"The ${animal} jumped over the ${target}."

If "animal " ="quick brown fox" and "target" = "lazy dog", then the above text will be converted to

"The quick brown fox jumped over the lazy dog."

When you are using variables in a feature file, GingerSpec will try to replace the variable with the appropriate value before the step is executed.

Creating local variables

You can store variables in a step to use it in any following step from the same scenario, like this

      Scenario: Saving and replacing local variables
        Given I save '2' in variable 'MY_LOCAL_VAR'
        Then '${MY_LOCAL_VAR}' is '2'

Another example:

    Scenario: A successful response with a valid body is returned
        Given I securely send requests to 'jsonplaceholder.typicode.com:443'
        When I send a 'GET' request to '/posts'
        Then the service response status must be '200'
        And I save element '$.[0].userId' in environment variable 'USER_ID'
        Then '${USER_ID}' matches '1

Or

    Scenario: Store the value returned by a query in an environment variable
        Given I connect with JDBC to database 'mysql' type 'mysql' on host '${MYSQL_HOST}' and port '3306' with user 'root' and password 'mysql'
        When I execute query from 'sql/createWeather.sql'
        When I execute query from 'sql/selectWeather.sql'
        Then I save the value of the row number '1' and the column with name 'city' in environment variable 'CITY'
        Then I save the value of the row number '2' and the column with name 'temp_hi' in environment variable 'TEMP_BARCELONA'
        Then '${CITY}' matches 'Caracas'
        Then '${TEMP_BARCELONA}' matches '37'

Variables created within a scenario are only valid within that scenario and not in any other. This is not a bug in GingerSpec, this is by design. The scenario represents a test case, and sharing variables between scenarios is not a good practice, scenarios should be independent of each other!

Injecting system variables

You can inject command-line arguments and use them as variables in your features, for example, you can inject the variable "URL" as a command-line argument in the following way:

mvn verify -DURL=http://demoqa.com/text-box

Sometimes the Windows terminal does not like certain characters when passing variables as a VM argument. If you stumble upon an error when passing variables in Windows, try using quotes ('') like this:

mvn verify -D'URL'='http://demoqa.com/text-box'

And then use it in your Feature file as any other variable:

    Scenario: Fill the form and click the submit button
        Given I go to '${URL}'
        And I type 'John' on the element with 'id:userName'

You can also use the syntax ${sys:MYVAR} to force GingerSpec to use the system variable

Default values

You can set a default value for unresolved variables. The default value for a variable can be appended to the variable name after the variable default value delimiter. The default value of the variable default value delimiter is ":-", as in bash and other *nix shells.

For example, given "animal" = "quick brown fox" and "target" = "lazy dog", then this:

"The ${animal} jumped over the ${target} ${undefined.number:-1234567890} times.";

Will be transformed to:

"The quick brown fox jumped over the lazy dog 1234567890 times."

Using interpolation

StringSubstitutor allows the use of interpolators, which are compact functions that can generate a specific output. StringSubstitutor contains this set of default interpolators

1. Base64 Encoder:         ${base64Encoder:HelloWorld!}
2. Java Constant:          ${const:java.awt.event.KeyEvent.VK_ESCAPE}
3. Date:                   ${date:yyyy-MM-dd}
4. DNS:                    ${dns:address|apache.org}
5. Environment Variable:   ${env:USERNAME}
6. File Content:           ${file:UTF-8:src/test/resources/document.properties}
7. Java:                   ${java:version} 
8. Localhost:              ${localhost:canonical-name}
9. Properties File:        ${properties:src/test/resources/document.properties::mykey}
10. Resource Bundle:       ${resourceBundle:org.example.testResourceBundleLookup:mykey}
11. Script:                ${script:javascript:3 + 4} 
12. System Property:       ${sys:user.dir}
13. URL Decoder:           ${urlDecoder:Hello%20World%21}
14. URL Encoder:           ${urlEncoder:Hello World!}
15. URL Content (HTTP):    ${url:UTF-8:http://www.apache.org}
16. URL Content (HTTPS):   ${url:UTF-8:https://www.apache.org}
17. URL Content (File):    ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/document.properties}
18. XML XPath:             ${xml:src/test/resources/document.xml:/root/path/to/node}

Alternatively, Gingerspec also contains:

19. To Upper case:               ${toUpperCase:Quick brown fox} (transforms the text to upper case)
20. To Lower case:               ${toLowerCase:Quick brown fox} (transforms the text to lower case)
21. Property file value:         ${envProperties:my.key} (this provides similar functionality as the old #{my.key})
22. Fake data:                   ${faker:expression} (generates random data for the given expression. See ¨Generating fake data¨ below)
23. Evaluate math expressions    ${math:expression} (evaluates the given mathematical expression. See ¨Resolving mathematical expressions¨ below)

Using Recursive Variable Replacement

If a variable value contains a variable then that variable will also be replaced (i.e ${${name}}). Cyclic replacements are detected and will throw an exception.

So, for example, you could do the following:

    Scenario: Operations with global variables  
      Given I save 'Foo' in variable 'VARNAME'
      Then '${toUpperCase:${VARNAME}}' matches 'FOO'  
      Then '${toLowerCase:${VARNAME}}' matches 'foo'

Using environment-dependent variables

During testing and development, it is very common to use different environments for the different stages of a product's lifecycle: for example, there is a production environment, but there might also exist a preproduction environment, a UAT environment, an Integration environment, etc. For the most part, the tests executed in each environment are the same, but things like IP addresses, databases URLs, users credentials change between one environment and another (for example, the URL of the production site is different from the URL of the preproduction or testing site).

GingerSpec allows the use of separate sets of *.properties files to store the specific variable's values of each environment. Consider the following examples.

Let's say you have to test an API that is running in production, and you have the following Scenario:

    Scenario: Check a successful response with a valid body is returned
      Given I securely send requests to '${envProperties:server.url}'
      When I send a 'GET' request to '/posts'
      Then the service response status must be '200'

GingerSpec will try to locate the value of the property server.url in the file /resources/configuration/common.properties (common.properties is the default file where GingerSpec will search for variables when using ${envProperties:my.key}). So, assuming common.properties contain the variable server.url=production-server-url.com, the Scenario will be executed as:

    Scenario: Check a successful response with a valid body is returned
      Given I securely send requests to 'production-server-url.com'
      When I send a 'GET' request to '/posts'
      Then the service response status must be '200'

Now, let's say that for the preproduction environment, I want server.url value to be preproduction-server-url.com instead of production-server-url.com. For this, create the file /resources/configuration/preprod.properties and add the variable server.url=preproduction-server-url.com. Now, to force GingerSpec to use the variables from the preprod.properties file, you have to use the maven variable env and pass the name of the file, like this:

     mvn verify -Denv=preprod

Your Gherkin Scenario will be executed as:

    Scenario: Check a successful response with a valid body is returned
      Given I securely send requests to 'preproduction-server-url.com'
      When I send a 'GET' request to '/posts'
      Then the service response status must be '200'

You can create as many properties files as you need: production.properties, integration.properties, uat.properties, etc. And force GingerSpec to use one or the other in the same way (-Denv=production, -Denv=integration, -Denv=uat, etc).

GingerSpec will first try to look for the variable in the given file. So, if you add -Denv=int, GingerSpec will try to locate the value in /resources/configuration/int.properties, if you use -Denv=uat GingerSpec will try to locate the value in /resources/configuration/uat.properties, etc. If the given variable is not found in the file, GingerSpec will try to locate the variable in the common.properties file (hence, the name common, it stores variables which value is common to all environments).

-Denv is a VM variable. You can set this variable when you execute your tests via the CLI or you can set this as a VM argument when running tests using your IDE.

You can also specify any other properties file outside the /resources/configuration folder, for example ${properties:src/test/resources/document.properties::mykey}

Generating fake data

GingerSpec comes preloaded with the Java Faker library. This library allows you to generate random/fake data for a wide range of cases: fake emails, fake addresses, random numbers and many many more. GingerSpec comes with a special interpolator that allows you to evaluate Java Faker expressions inside variables

To make use of this interpolator, you will have to use the keyword ¨faker:expression¨ where ¨expression¨ is any expression valid for the Java Faker library. Examples of valid expressions would be Internet.emailAddress, Name.first_name, Address.cityName, etc. GingerSpec will automatically resolve the expression in the step before it is executed.

Take a look at how to generate fake data in your scenario:

  Scenario: Using the faker library
    Given I save '${faker:number.number_between '1','10'}' in variable 'RANDOM_NUMBER'
    Given I save '${faker:Internet.emailAddress}' in variable 'RANDOM_EMAIL'
    Given I save '${faker:Name.first_name}' in variable 'RANDOM_NAME'

GingerSpec will generate fake data for the given expressions before the step is executed, for example:

  Scenario: Using the faker library
    Given I save '6' in variable 'RANDOM_NUMBER'
    Given I save '[email protected]' in variable 'RANDOM_EMAIL'
    Given I save 'Calvin' in variable 'RANDOM_NAME'

If you need to specify a Locale (for example, you need to generate random street names from a different country), you can do it with ¨faker:locale:expression¨, where locale is any of the supported locales

You can also generate fake data directly in your Java code:

Faker faker = new Faker();

String name = faker.name().fullName(); // Miss Samanta Schmidt
String firstName = faker.name().firstName(); // Emory
String lastName = faker.name().lastName(); // Barton

String streetAddress = faker.address().streetAddress(); // 60018 Sawayn Brooks Suite 449

Resolving mathematical expressions

GingerSpec also contains a special interpolator capable of evaluating mathematical expressions in variables thanks to the use of the exp4j library.

To make use of this interpolator, you will have to use ¨math:expression¨ where ¨expression¨ is any valid mathematical expression. For example ¨3 * sin(90) - 2 / (5 - 2)¨. Since GingerSpec can resolve variables within variables, that means that you can create a variable in a previous step and then use it within a mathematical expression in the following step, for example:

  Scenario: Doing a simple calculation
    Given I save '2' in variable 'X'
    Given I save '2' in variable 'Y'
    Given '${math:${X} + ${Y}}' is '4.0'

Of course, more complex mathematical expressions can be evaluated

  Scenario: Doing an advanced calculation
    Given '${math:3 * sin(90) - 2 / (5 - 2)}' is '2.015323324135007'

Resolving variables in files

You can use variables directly in your json files when you execute a rest request using any of the GingerSpec steps for testing rest APIs. For example, consider the following json file stored in 'schemas/mytestdata.json'

{
  "userId": 1,
  "email": "${faker:Internet.emailAddress}",
  "name": "${faker:Name.first_name}",
  "description": "The ${animal} jumped over the ${target}",
}

When I execute the following scenario:

Scenario: A new element is inserted via a POST call
        Given I securely send requests to 'jsonplaceholder.typicode.com:443'
        And I save 'fox' in variable 'animal'
        And I save 'lazy dog' in variable 'target'
        When I send a 'POST' request to '/posts' based on 'schemas/mytestdata.json' as 'json'
        Then the service response status must be '201'

GingerSpec will resolve the variables before attaching the body to the POST request, so, for example, the final body being sent could be something like:

{
  "userId": 1,
  "email": "[email protected]",
  "name": "Calvin",
  "description": "The fox jumped over the lazy dog",
}

GingerSpec provides an special function inside the CommonG class. You can use this function when creating your own steps and all variables present in the text will be first resolved before returning the content as a String object