restest

Fun way to test RESTful APIs

restest

restest solves 2 problems in REST API testing:

  • It makes you more productive.
  • Tests are more maintainable.

Installation is simple.

npm install restest -g

To run the tests mentioned in this README, please do the following:

npm install restest --save-dev

Now, cd to node_modules/restest directory and issue the command:

node examples/restest-test-server.js

This will bring a test server that supports create/read/update/delete operations for a user resource.

Now, create a file tests.yaml and start describing your tests:

# This is the id of the test.
create_user:
 
    # You can tag your tests. This is super useful during execution when you
    # want to run tests with specific tags.
    tags: [crud, user]
 
    # Each test is a series of API request and expected responses. We call them
    # steps. This test has only one step.
    steps:
 
        # Each test step has 5 parameters: uri (required), http method (deafult
        # GET), request payload (optional), expected status (default 200) and
        # expected response ( optional)
      - uri: "http://localhost:3030/restest-examples/users"
        method: "POST"
        payload:
            name: "Ram Sharma"
            email: "ram@sharma.com"
        expected_status: 200
        expected_response:
            name: "Ram Sharma"
            email: "ram@sharma.com"

Now, to run the test

restest tests.yaml

It's bad to hardcode your hostname and port in the test. Solution to that is restest variables. You can define some variables while invoking restest and use them throughout your tests. So, you could re-write the uri in your test as:

    - uri: "{{uri_prefix}}/users"

and invoke restest as:

restest -d uri_prefix="http://localhost:3030/restest-examples" tests.yaml

You can use your variables anywhere you use a string in uri, method, payload & expected_response.

Your response can have deeply nested objects and you can still expect them. Example:

create_user:
    tags: [crud, user]
    steps:
      - uri: "http://localhost:3030/restest-examples/users"
        method: "POST"
        payload:
            name: "Ram Sharma"
            email: "ram@sharma.com"
            phones:
              - type: home
                number: 123456789
              - type: office
                number: 987654321
            address:
                city: Bangalore
                country: India
        expected_response:
            name: "Ram Sharma"
            email: "ram@sharma.com"
            phones:
              - type: office
                number: 987654321
              - type: home
                number: 123456789
            address:
                city: Bangalore
                country: India
 

Note about the phone number, restest doesn't check for order while matching arrays.

Now, let's run a test that would create a user and verify the creation by doing a GET.

create_and_get_user:
    tags: [crud, user]
    steps:
      - uri: "{{uri_prefix}}/users"
        method: "POST"
        payload:
            name: "Ram Sharma"
            email: "ram@sharma.com"
        expected_response:
            name: "Ram Sharma"
            email: "ram@sharma.com"
 
      - uri: "{{uri_prefix}}/users/{{responses.[0].id}}"
        expected_response:
            name: "Ram Sharma"
            email: "ram@sharma.com"
 

Did you notice that we've added another step in our test now? The second step does a GET (default method) by user id and expects the same name & email as we gave in previous step.

We'll talk about the {{responses.[0].id}} in next section.

Each string value in the test (be it uri, method, payload values or expected response values) are evaluated using Handlebars templating engine. And during template evaluation, following variables are set:

  • All variables specified using -d command line flag.
  • payloads and responses are special variables. These are arrays of payloads sent & responses received during this test. In this case, when the first step completes, it's payload object is pushed into payloads array and received response object is pushed into responses array.
  • this_payload is another special variable. It's available in the expected_response section and holds the entire payload sent as part of current request.

So, the following line:

      - uri: "{{uri_prefix}}/users/{{responses.[0].id}}"

{{responses.[0].id}}`` points to the value ofid` in the response received in step 1.

We could re-write the entire test as follows:

create_and_get_user:
    tags: [crud, user]
    steps:
      - uri: "{{uri_prefix}}/users"
        method: "POST"
        payload:
            name: "Ram Sharma"
            email: "ram@sharma.com"
        expected_response:
            name: "{{this_payload.name}}"
            email: "{{this_payload.email}}"
 
      - uri: "{{uri_prefix}}/users/{{responses.[0].id}}"
        expected_response:
            name: "{{payloads.[0].name}}"
            email: "{{payloads.[0].email}}"
 

Given that we'll need to create a user for multiple tests, it would be good to templatize it and use the template in tests.

Let's rewrite the previous example with templates:

# A template for creating user
tmpl_create_user:
    uri: "{{uri_prefix}}/users"
    method: "POST"
    payload:
        name: "Ram Sharma"
        email: "ram@sharma.com"
    expected_status: 200
    expected_response:
        name: "{{this_payload.name}}"
        email: "{{this_payload.email}}"
 
 
create_and_get_user:
    tags: [crud, user]
    steps:
      - template: tmpl_create_user
 
      - uri: "{{uri_prefix}}/users"
        method: "POST"
        payload:
            name: "Ram Sharma"
            email: "ram@sharma.com"
        expected_response:
            name: "{{this_payload.name}}"
            email: "{{this_payload.email}}"
 
      - uri: "{{uri_prefix}}/users/{{responses.[0].id}}"
        expected_response:
            name: "{{payloads.[0].name}}"
            email: "{{payloads.[0].email}}"
 

Let's see what we did around here. First we created a template for API that can be used for creating user:

# A template for creating user
tmpl_create_user:
    uri: "{{uri_prefix}}/users"
    method: "POST"
    payload:
        name: "Ram Sharma"
        email: "ram@sharma.com"
    expected_status: 200
    expected_response:
        name: "{{this_payload.name}}"
        email: "{{this_payload.email}}"

A template has five attributes: uri, method, payload, expected_status and expected_response. Same as any test step.

To use a template in a test step, just use the keyword template:

      - template: tmpl_create_user

Let's revisit the create_user test with the use of tmpl_create_user template that we just created:

create_user:
    tags: [crud, user]
    steps:
      - template: tmpl_create_user

Now, what if we wanted to provide a different email id instead of the one provided in template? And what if we want to also test the address functionality? Here is how you can do it:

create_user:
    tags: [crud, user]
    steps:
      - template: tmpl_create_user
        payload:
            email: "another@email.com"
            address:
                city: Bangalore
                country: India
        expected_response:
            address:
                city: Bangalore
                country: India
 

restest picks up the request parameters (i.e. uri, method, payload, expected_status & expected_response) from the step (if specified) and merges them into the parameters specified at the template level.

In this example, payload will be taken from the template, email field of the template payload will be over-ridden by what's specified in test step. address field will be added now.

restest uses underscoreDeepExtend for merging payload & expected_response objects. Please refer to its documentation about extension related details.

Download the jekyll template from https://github.com/mangarg/restest-jekyll-template. Unzip it in some directory.

Now run the following command:

restest -r jekyll -o <restest-jekyll-template-dir> <files...>

restest will execute your tests and populate the jekyll directory with API documentation.