06. The Dashboard

The API Fortress platform has a full featured dashboard built into the platform. The dashboard allows you to:

  • View all test logs across teams
  • Share, download, or print test logs
  • View all performance metrics from tests (latency & fetch)
  • Set performance alerts
  • Filter logs and alerts by time, endpoint, and more

 

There are also some pointers we would like specify:

Different ways to compose a Request Body

In this post we will show you the different ways you can compose a Request Body: from the simplest to the most complex.

  1. Copy and paste the body from somewhere

    The first and easiest way is when we have a body from somewhere to copy and paste as is into the call. Let’s see how this is done:

    1. In the composer we add the POST component and type the url and all of the required fields.
      Url: https://mydomain.com/myPost (the url of the resource you want to test)
      Variable: payload (the name of the variable that contains the response)
      Mode: json (the type of the response)

      post_comp

    2. Now we add the Body component and after selecting the Content-Type we paste the body in Content field.
      Content-Type: application/json
      Content: {"method":"post","url":"http://www.testme.com/api/run/test"} (the body required in your call)

      paste_body

    3. Now we can execute the call and proceed with the test.
      final_call
  2. Using Variables in the Request Body

    Another way to compose a request is using variables in the body.

    1. In the composer we add the POST component typing the url and all the required fields.
      Url: https://mydomain.com/myPost (the url of the resource you want to test)
      Variable: payload (the name of the variable that contains the response)
      Mode: json (the type of the response)

      post_comp

    2. We add the Body component. In the Content-Type we choose the proper one, let’s say application/json. In this scenario we need to use a variable so in the Content field we enter the following:
       {
       "user": ${user},
       "password": ${password},
       "url": "http://www.testme.com/api/run/test"
       }

      In this scenario “user” and “password” are not directly passed in the body but they are variables defined as global parameters in the data set.
      var_body
      data_set

    3. The POST has been done and can be executed.
      final_call2
  3. Using a Variable from another call

    The next way to compose a Request Body is by using a variable from another call. Let’s see how this can be done.

    1. The first thing we need to do is add the call we will retrieve the variable from. Let’s consider, as example, the common scenario where we need to perform a login for authentication and retrieve the authentication token required for the following call.
      Url: http://www.mydomain.com/login (the url of the resource you want to test)
      Variable: payload (the name of the variable that contains the response)
      Mode: json (the type of the response)
      Header: 
         Name: Authorization
         Value: Basic YWRtaW46cGFzc3dvcmQ= (this value comes from encoding username:password in Base64)

      loginAuth

    2. Executing the login we will have as response the desired token. Let’s see it using our console.
      response_token
    3. Now we need to save the token as variable.
      Var: token (the name of the variable)
      Variable mode: String (the type of the variable)
      Value: ${payload.access_token} (we retrive the value from the previous 'payload')

      var_token

    4. Once the token has been saved as variable we can proceed adding the second call and use that token in the Request Body.
      Content-Type: application/json
      Content: {"token":"${token}"}

      bodyWToken

  4. Using an object from another call

    In the next example we will show you a more complex case. We will consider the scenario where we need to use an object retrieved from a previous call into the body of a subsequent call. Let’s take a look at an example:

    1. First, we perform the call we retrieve the object from.
      search
    2. Let’s execute the call in our console in order to see the response.
      {
       "id": 123,
       "items": [
       {
       "id": 11,
       "name": "stuff1"
       },
       {
       "id": 12,
       "name": "stuff2"
       },
       {
       "id": 13,
       "name": "stuff3"
       }
       ]
      }

      search_response

    3. Let’s say we need the object ‘items’ as the body in the subsequent call. So, as a second call, we will add a POST and we will type the following as body:
      ${searchPayload.items.asJSON()}

       

      objectInBody

    4. Now we can proceed with the test.

     

  5. Creating a new structure to add as a body

The last scenario is yet another more complex one. In this case, we consider the scenario where we need to create a new structure to add as a body, using data from a previous call. Let’s see how we can do this:

  1. The first thing we have to do is to perform the call which retrieves the data we’re using. Let’s consider a GET that returns an array of items.firstCall
  2. Let’s see the response using our console.
    {
     "items": [
     {
     "id": 11,
     "price": 5.99
     },
     {
     "id": 12,
     "price": 6.99
     },
     {
     "id": 13,
     "price": 10.99
     },
     {
     "id": 14,
     "price": 15.99
     }
     ]
    }

    response_get

  3. Now we need to create the new data structure. To do so, we add a SET component as follow: 
    payload.items.each { it -> it.currency='$'  }; return payload.asJSON(); (for each item in the array, we add the currency attribute with "$" as value)

    newData

  4. Now we can add the POST and add the new structure as the POST request body:postWithNewStructure
  5. That’s it. Now we can proceed with the test.allDone

Note: in this post we have used the POST method but all of the examples shown can be applied to the other REST methods. In the same way, we have demonstrated scenarios with Request Bodies, but all of the examples can be used for Header or Param cases.

 

 

Cloud vs On-Premises (local)

Note: Did you receive an Invalid Request message? It’s possible that you are trying to reach an environment that is behind a firewall that our public cloud accounts can’t reach. You may need to whitelist our IPs, or install an on-premises trial. Please contact support@apifortress.com to learn more, or use our chat bot.

The API Fortress platform was uniquely developed to function in our cloud, or from your own virtual private cloud or data center. This was to allow for customers that have security requirements that don’t allow for use of the cloud version (like private staging environments). The entire platform can be deployed behind your own firewall using Docker, Kubernetes or OpenShift.

To trial cloud simply register an account. To trial on-premises please fill out this form and contact sales@apifortress.com to learn more.

Why On-Premises?

There are more details on the on-premises option here, but the main reasons for choosing on-premises over cloud are:

  • On-premises can have access to environments behind firewalls, like staging and preprod.
  • On-premises is unlimited in terms of tests and executions.
  • Load Testing and Mocking are currently only available on-premises.
  • More control over scheduling, database choices, etc…

An on-premises deployment is simple to do. Please contact sales@apifortress.com and we’ll help you get setup.

Footprint

Consider a scenario where a route has a parameter in it. Let’s take a look at an example:

http://www.whereever.com/<id>/details

Each individual rest run for this route will produce a new line in the metrics view:
http://www.whereever.com/1/details
http://www.whereever.com/2/details
http://www.whereever.com/3/details
http://www.whereever.com/4/details

This sort of reporting will quickly turn our dashboard into a disorganized mess. To produce a single endpoint for reporting from each one of these calls, you can use what we call a ‘footprint.’

How is this accomplished? In the test, you need to add a Config component to the I/O component as seen below:

configFootprint

The Config component has two fields:
Name: the name you want to assign. In this case, you MUST to enter ‘footprint’
Value: The value for the configuration component. To set up a footprint, you must enter the same URL that’s in the I/O Component. Any parameterized portion of the URL must be wrapped in square brackets.

Based upon our example, the value in this case would be:

http://www.wherever.com/whatever/[id]/details

In the project dashboard, after every run of the test instead of
http://www.whereever.com/whatever/1/details
http://www.whereever.com/whatever/2/details
http://www.whereever.com/whatever/3/details
http://www.whereever.com/whatever/4/details

you will find only one endpoint, displayed as:
http://www.whereever.com/whatever/[id]/details

For each endpoint you can use more square brackets, one for each variable that could assume multiple values. For example: http://www.whereever.com/[whatever]/[id]/details/[colors]/whatever

When you write the value of the config, for the ‘static’ part of the endpoint you can also call a variable as in any I/O operation.

Example: ${protocol}/${domain}/[whatever]/[id]/details/[colors]/whatever

is valid syntax.

 

 

Dynamic dates

Have you ever needed to pass a future date as part of the request inside of a test? Perhaps as a check-in or check-out date? You could enter it as static value, but that means you would have to periodically update the date as time went on. Creating a dynamic date in API Fortress is a simple solution for this sort of situation.

Here’s the procedure:

  1. First, open the Composer and add a Set (variable)
    setVar
  2. Then, enter the variable name and leave the mode as String
    varComp
  3. Lastly, enter the following string in the Value field:${D.format(D.plusDays(D.nowMillis(),35), ‘yyyy-MM-dd’)}

valueField
Let’s analyse what this string means:

D.nowMillis(): returns the current Unix epoch in milliseconds
D.plusDays(): returns the provided milliseconds, plus the provided number of days (in our example, we have added 35 days to today date)
D.format(): creates a timestamp with the given format, using the current timezone (in our example yyyy-MM-dd)

As result, you will have something like 2018-05-15

You can obtain a past date, starting from todays date with the following string:
${D.format(D.minusDays(D.nowMillis(),35), ‘yyyy-MM-dd’)}

You can also create a date based on a specified timezone:
${D.format(D.plusDays(D.nowMillis(),35), ‘yyyy-MM-dd’,’America/New_York’)}
The above string create the same date as our first example using New York (EST) as the timezone.

For more details about you can check our reference page

 

How to create a dynamic header

Most APIs have only one response format, either JSON or XML. But what should we do in the case of an API endpoint that can return either JSON OR XML? We could write two different tests, with one supporting each response type, but we’d be repeating a good amount of code in both tests. API Fortress allows you to test both, using the same I/O component and assertion components for almost all test cases. In a few cases, we’ll need to add a bit of extra logic to allow the platform to distinguish between the two formats.

Let’s consider the scenario where you need to pass in the Header the Accept value that is ‘application/json’ if you are testing the JSON and ‘application/xml’ if you are testing the XML. Usually in this case, you should make two different calls, as shown in the image below, in order to be able to pass the different values in the header:

Let’s consider an example. The API call in question requires an “Accept” header. This “Accept” header needs a value of “application/json” if you are testing the JSON case and “application/xml” if you are testing the XML case. Below, we can see the solution to this problem that requires setting up two separate calls. It’s not particularly adherent to the DRY rule of programming. (Don’t repeat yourself!)

both_calls
API Fortress allows you to solve this issue by making only one call.

    1. Let’s start adding the different formats as variables, as seen below.inputSets
    2. Now, we can remove one call and add the format variable in the “Mode” input.
      varFormat
    3. The header is still static at this point. We need to turn it into a dynamic one which changes to suit the data type of the API we are testing. We add a variable component above the I/O operation that will return the appropriate value.setDynamVar
    4. if (format == 'xml')
      return "application/xml";
      else return "application/json";
      To explain: the ‘acceptHeader’ variable will have ‘application/xml’ as value if format is ‘xml’ and ‘application/json’ otherwise (since we have only two different formats, it will be ‘application/json’ only for JSON format
    5. Now, we can finally remove the ‘static’ header and add the ‘dynamic’ header by changing the Header value to ‘${acceptHeader}’dynamicHeader
      Now, the test will be executed two times; once for ‘XML’ and once for ‘JSON’, ensuring that the header will have the correct value.

GitHub – How to Use Files as Datasource (Large or Small Datasets)

Github is a valuable platform to use when you want to pull files and use them as a datasource. Some examples are CSVs and JSON files. Below is a walk through on how to make them work.

Set up GitHub

  • 1When creating the token make sure you’re selecting the public_repo subcategory or the repo category based on which type of repository that will host your input data.2
    Keep in mind that GitHub will show you the token only once, so make sure you’re copying and pasting it somewhere safe and accessible.
  • Create a repository that will host your input data if one doesn’t exist already.
  • Commit and push a data source file. It can be a CSV, JSON or XML file. We will use a CSV file for the purposes of this example.3

    Create a simple test

  • Create a test
  • Introduce the GitHub component and configure it accordingly4
  • Account is your GitHub username
  • Repository is the name of the repository that your data file is pushed to.
  • Branch is the repository branch that the desired version of the data file is in.
  • Token is the token described above, generated in the GitHub platform.
  • Variable is the variable that the payload will be stored under.
  • Path is the name of the file in the repository.
  • Mode is the filetype of the file in the repository.
  • The system will retrieve the document, parse it as a CSV file and assign it to the inputData variable
  • (optional) Verify that everything is set up correctly by adding a comment printing the parsed data as in:5
  • (Optional) Run the test:6
    NOTE: API Fortress will parse a CSV file as an array (rows) of arrays (columns), so access to the data is positional. 
  • Now, let’s iterate over a subset of this input set. Introduce a selection strategy if necessary:
    7This will iterate over a subset of 5 randomly selected items. Other strategies are described in Appendix A
  • (Optional) Within each iteration, we suggest that you introduce a comment that will help you identify which item you’re looking at for debugging purposes.8 9
  • Use the data to perform your HTTP call:10
  • Introduce some assertions for testing purposes11
  • And run it12

Appendix A: Selection Strategies for Large Datasets

Simple selectors

  • None. If the number of iterations is greater than 100, the system will randomly select 100 elements, unless you override the maximum iterator size.
    • Pick(n). Ask the system to randomly select a n-sized sample of elements.
      Example: inputData.pick(5)

 

  • Slice. If you’re interested in using a specific slice of data from the inputData, you can slice it according to your needs.
    Example: inputData[10..20] (will select items from index 10 to index 20)

 

Advanced Slicing

Assume you have a 1000 lines CSV file and you need to use them all. While this is technically possible (by overriding the maximum number of iterations) the usefulness of the test may vary on:

  1. How long the HTTP request takes
  2. How complex the test is going to be
  3. The number of errors the test may trigger

Moreover, the readability of the resulting document may degrade when trying to debug an issue.

Here’s a slicing technique we suggest to ease these points.

  • Introduce the following 2 variables in the global parameters:

    13
  • Use the following expression in your each statement:
    inputData[offset.toInteger()..offset.toInteger()+limit.toInteger()]
    Which reads: slice inputData from the offset index to the offset+limit index

    Note: the toInteger() command is required as variables are always strings and we need to work with numbers.

    By doing so we are setting a baseline: as a default test input data from index 0 to index 99.
  • Introduce as many environments as the slices count, overriding the offset variable15
    Now you can run the test on specific 100 elements slices, by selecting the environment.16 1718
  • Finally, you can schedule your slices accordingly:19

Following redirects

By default, API Fortress handles redirects as per RFC standard. So if you are performing a GET and the response wants to redirect you somewhere else, you don’t need to do anything since the redirect will be followed automatically.

Now, since the RFC says that a redirected I/O call should perform the same operation as the original call, and given that, say, re-posting, is a security threat, API Fortress will not automatically follow redirects for any other I/O operation.

Boring, you may say, but following the exact RFC specifications is how you make sure that any client will not be caught off guard!

This specific scenario can be handled applying the following steps:

Make the I/O operation (let’s consider a POST as an example)

  1. step1
    The response header contains the ‘Location’ field. Let’s make sure it’s there, by performing the call in our console tool.step2highlighted
  2. Craft a GET to the ‘Location’ retrieved in the response header of the previous call (i.e. ${payload_response.headers[‘location’]})

    Post307

And you’re done.

Enabling API Fortress to Read Local Files

Using the read-file command, you can have your test read local files.

Currently there is no GUI functionality to upload the files, however, you can set up your container to connect to a local folder on your host machine.

To do so, you have to update your docker-compose.yml file in the core/ directory.

In the “apifortress” service definition, modify the “volumes” block by adding one entry looking like this:

volumes: 
    - /var/local/data:/data

Where /var/local/data is the path in your host machine where you want to store the files.

09. Using Variables for Environment Flexibility

In API Fortress, almost any string can be hardcoded or referenced as a variable. Hardcoding is fine as long as you’re building simple tests, however, when:

  • The number of tests is increasing
  • The complexity of tests is increasing
  • The number of tested environments is increasing

It is advisable to parametrize some items. Most of the parametrization you will likely do relates to the HTTP request itself.

Using the Vault

For example, while this is perfectly legit:

harcodedIt may become extremely painful to update tens if not hundreds of tests if the domain changes.

Using the vault to store domain names is a good way to solve this problem by simply adding a “domain” variable in your vault as in:

domainAnd editing the GET like this:

parametrizedIn this way, editing the vault variable will instantly affect all tests.

Using the Environments

Once the domain is parametrized, no one stops you from overriding the variable, if needed.

By activating a preset in the environments section as in:

env2You will be able to hit a different domain in the current session, without actually changing the test.

The same selection can be performed while creating a schedule, to create specific runs hitting specific environments.

In Request Bodies

Variables are not only bound to URLs. Request bodies can also be handled like “templates” when needed, incorporating variables as in:

bodyAnd Basically Anywhere

Variables can be referenced basically anywhere, let’s take this assertion in consideration:

expYes, we’re using variables as expected values.

However…

The platform not only provides you the flexibility but also the freedom to basically combine everything as you want.
With that said, we would also provide you some hints on how to get the best from your experience.

  1. Fill the vault with data that is actually project-wise. Domains, protocols, API keys. They are all fine. We discourage you to introduce test specific variables in here because it would produce an overhead of information that will be unused most of the time,
  2. Fill the globals/input set with test specific variables. Such as paths, Ids, dates, serial numbers,
  3. Make sure that vault + globals/input set make up a complete variable scope for the test. In other words, the test should be able to run without further information,
  4. Use the environments to change the values of the variable scope generated by the vault+global/input sets,
  5. Don’t overdo. Parametrize stuff that can actually change and leave everything else as static strings. Variables are… well, variable, so an excessive and unnecessary use of variables leads to uncertainty and hard to track behaviors.

parametrization, pass values, pass variables, header variables, body variables