API Testing Custom Steps

  • Most of the custom steps are concerned with data acquisition, e.g. they wrap access to some APIs and make their output available as a test step
  • In the example below, we add implement a step that connects to a currency exchange rate service to check what the current exchange rate of EUR is
  • The code of custom steps is kept in a file called steps.py that is inside your apitest project.
  • You can find the code below in "steps.py" already as it's a built-in example of a how custom step looks like. This is why all the Python imports are omitted, you can find them in "steps.py".
@given('I save the exchange rate of EUR to "{currency}" under "{name}"')
def i_save_the_euro_exchange_rate(ctx:'Context', currency:'str', name:'str') -> 'None':

    # The endpoint that gives us exchange rates ..
    address = 'https://api.frankfurter.app/latest'

    # .. invoke it ..
    response = requests.get(address)

    # .. extract the JSON response ..
    data = response.json()

    # .. extract rates ..
    rates = data['rates']

    # .. get the rate for the input currency ..
    rate = rates[currency]

    # .. and store it for later use.
    ctx.zato.user_ctx[name] = rate

This is a regular Python function whose main purpose, in lines 5-20, is to connect to an API endpoint, get a JSON response and extract some data from it.

This principle will be almost always true, the main part of a custom step won't be about apitest itself but about a piece of functionality, perhaps a part of a business process, that you want to wrap in a test step.

Let's discuss the apitest-specific, highlighted lines then.

  • Line 1: You use the @given decorator with a string pattern of your choice. Parameters that are variables, such currency and name, are always in quotation marks "" and between curly brackets {}.
  • Line 2: The @obtain_values decorator is always needed for the test to be able to extract values from its environment and context
  • Line 3: You implement a regular Python function that has a context (configuration) object on input as well as all the parameters that you previously used in the pattern. Note that the name of the parameters in the function must be the same as in the pattern.
  • Line 21: You create test variables by assigning them to the ctx.zato.user_ctx dictionary. Anything you assign to it will be later available in other test steps.

Taken together, this lets us have this:

Given I save the exchange rate of EUR to "USD" under "exchange_rate"
Given request is "{}"
Given path "/rate" in request is "#exchange_rate"

You can now observe how the value of the exchange rate was saved in the "exchange_rate" test variable and how it can be used by a different step by prepending the pound sign # to it.

A couple more points:

  • All the built-in steps in apitest are implemented in the same manner. It means that you can open their source code in your apitest installation, check how they work and base your own steps on them.

  • Don't delete anything from the "steps.py" file. Even if you add your own steps, don't delete the code that you can find there by default.

Read more