One of the most powerful features of Artillery is being able to add custom logic to your test scripts by writing JS functions. If you’re a Node.js developer, you will already know that npm contains a lot of high-quality open-source code, and any of that code can be used in your Artillery tests scripts to get stuff done faster or to accomplish something that Artillery does not provide out of the box.

Generating Realistic Random Data For Tests

We can use Artillery’s support for custom JS to solve a common task when writing performance testing scripts: making request payloads sent to our backend APIs random, while making sure that they are also realistic.

For example let’s say our test scenario contains a user registration step, which is a POST request to /users with a JSON body containing the name, email address and password for the new user:

- post:
    url: "/users"
    json:
      name: "Harry Potter"
      email: "harry.potter@alumni.hogwarts.ac.uk"
      password: "ExpectoPatronum1"

However, hardcoding the request data is not ideal. We’d definitely want to send a different combination of email, name, and password for every request actually made to the endpoint to make our tests more representative of real behavior. This is where the Faker.js package would come in.

Faker.js is a small module that has a comprehensive API and can be used to generate all sorts of random data: from names, email addresses and phone numbers to colors, numbers, and lorem ipsum text blocks.

Here’s how we could generate a name using Faker for example:

const Faker = require('faker')

// Print a randomly generated name:
console.log(`${ Faker.name.firstName() } ${ Faker.name.lastName() }`);

When run, the script produces output along the lines of:

Margarete Ernser
Annabell Huel
Bertrand Gerlach

We can easily combine Faker.js with Artillery to be able to send a lot of random data to an API we’re testing in just a couple of steps.

Using Faker.js With Artillery In 4 Steps

First, a quick refresher of how load testing with Artillery works:

  1. We write one or more scenarios, which describe sequences of requests that represent how a user would interact with an API. For example, a typical scenario for the backend of a mobile photo sharing app could be: sign in to get a session, load the newsfeed, load extended information about a photo appearing in the newsfeed, and then leave a comment on that photo.
  2. To run a load test, we tell Artillery how many virtual users we want it to create and at what rate, for example: 20 new users per second for 60 seconds, followed by 50 users per second for 10 minutes.
  3. Each of the virtual users that Artillery creates is an independent execution of the steps in a scenario that we created in (1). Each virtual user can have one or more variables associated with it, which aren’t shared with other virtual users.

With that in mind, our goal for this exercise is to go from a script that looks like this:

config:
  target: "https://myapi.staging.example.com"
  phases:
    - duration: 60
      arrivalRate: 20
    - duration: 600
      arrivalRate: 50
scenarios:
  - name: "Register a user"
    flow:
      - post:
          url: "/users"
          json:
            name: "Harry Potter"
            email: "harry.potter@alumni.hogwarts.ac.uk"
            password: "ExpectoPatronum1"

To something that sends random data rather than hardcoded values.

Step 1

The first thing we’ll do is replace hardcoded values with variables:

    - post:
        url: "/users"
        json:
          name: "{{ name }}"
          email: "{{ email }}"
          password: "{{ password }}"

We don’t have those variables defined anywhere yet - they will be created with a custom function and Faker.js.

Step 2

Create a my-functions.js file in the same directory as our Artillery test script, and add config.processor to our script to tell Artillery to load custom JS code from a module:

config:
  target: "https://myapi.staging.example.com"
  processor: "./my-functions.js"

This will make any functions exported from my-functions.js available in our scenarios.

Step 3

We can make our scenario call a custom JS function as one of the steps in our scenario with the function command. Since our code will be creating the variables that the POST request to /users will be using, we’ll want to call our function that generates random data at some point before making the POST request. Our scenario now becomes this:

scenarios:
  - name: "Register a user"
    flow:
      # call generateRandomData() to create the name, email, and password variables
      - function: "generateRandomData"
      - post:
          url: "/users"
          json:
            name: ""
            email: ""
            password: ""

Step 4

By convention, a function called from a scenario with a function command is given 3 arguments: the virtual user’s context, an EventEmitter which can be used to communicate with Artillery, and a callback which needs to be called for the control to be returned to the scenario. (There are other ways to call functions, e.g. with a beforeRequest attribute on a request definition but we won’t be using those for our current task.)

Our module with the generateRandomData() function using Faker.js will then look like this:

'use strict';

module.exports = {
  generateRandomData
};

// Make sure to "npm install faker" first.
const Faker = require('faker');

function generateRandomData(userContext, events, done) {
  // generate data with Faker:
  const name = `${Faker.name.firstName()} ${Faker.name.lastName()}`;
  const email = Faker.internet.exampleEmail();
  const password = Faker.internet.password();
  // add variables to virtual user's context:
  userContext.vars.name = name;
  userContext.vars.email = email;
  userContext.vars.password = password;
  // continue with executing the scenario:
  return done();
}

And that’s it! Our complete test script now looks like this:

config:
  target: "https://myapi.staging.example.com"
  phases:
    - duration: 60
      arrivalRate: 20
    - duration: 600
      arrivalRate: 50
  processor: "./my-functions.js"

scenarios:
  - name: "Register a user"
    flow:
      # call generateRandomData() to create the name, email, and password variables
      - function: "generateRandomData"
      - post:
          url: "/users"
          json:
            name: "{{ name }}"
            email: "{{ email }}"
            password: "{{ password }}"
      # Print the data we're sending while debugging the script:
      - log: "Sent a request to /users with {{ name }}, {{ email }}, {{ password }}"

And if we run the script now, we should see output like:

Sent a request to /users with Shawn Jacobi, Ruth_Crona@example.org, pvUywGVO3mzWHyy
Sent a request to /users with Chaim Gottlieb, Dannie_Okuneva@example.net, 30CdOVqOMTF7HuB
Sent a request to /users with Damian Denesik, Lessie_Klocko@example.org, cb_BEnov9HYsZ5A
Sent a request to /users with Carlie Kuvalis, Etha71@example.org, ma5WRHEXLO1cuH5
Sent a request to /users with Theresa Koelpin, Jamir.Nikolaus13@example.org, thqUvuuVqtrhn9f
Sent a request to /users with Arthur Hagenes, Rosemary.Senger@example.com, TyPYsy9fiN1Ilhu
Sent a request to /users with Rosie O'Reilly, Leonor2@example.com, kb_biCWG_UXg66H
Sent a request to /users with Bailee McDermott, Gisselle_Gislason@example.net, bubSCY6BCZgpuSy
Sent a request to /users with Caleb Cole, Treva_Weber19@example.com, xFVVZxVS1Hh9vTe
Sent a request to /users with Adolphus Zieme, Christian67@example.com, x1dqigA2MmjC1qx

Closing Remarks

We saw that combining npm modules with Artillery tests is quite straightforward, and opens up a lot of possibilities for our test scripts. We can use a module like Faker.js to make our tests more realistic or even turn Artillery into a fuzz testing tool with a little more effort.

More information on writing custom JS can be found in the Artillery docs.