Asserting REST API Workflows

Tutorial on testing REST API endpoints with dependencies on other endpoints with Upssert

Tyrone Tudehope
Tyrone Tudehope: Blog

--

Originally appeared on https://tyronetudehope.com.

In a previous post I introduced Upssert and talked about how it can be quickly configured to run tests against a REST API. In this post, I’ll demonstrate how we can easily configure test cases with dependencies to previous tests in a suite.

Imagine you have an endpoint which accepts a username and password and returns and authentication token. Then, by using that token you can create and fetch resources. To start, let’s create a very simple server app which stores items in an in-memory datastore. The authentication does nothing other than compare user input against some hard coded values.

Start the server, then in another terminal, test whether you can make requests to it:

$ node index
$ curl -X POST http://localhost:8080/auth-token \
-d username=user \
-d password=passwd
{"auth-token":"secret"}
$ curl -X POST http://localhost:8080/items \
-d name="Hello World" \
-H "Authorization: Token secret"
{"id":"/items/1"}

Kill your server, and start it again so that we have a clean datastore. Because we know exactly what data is required, and what the authorization token should be, we can test the POST /items endpoint by creating test cases with static data. If you haven’t already, install Upssert:

$ npm install -g upssert

We’ll create a simple test case to test creating a new item:

Running the test suite should result in a pass:

$ upssert static-test.json
Executing 1 test suites (1 assertions)…
Create new item
✓ POST /items
1 passing (81ms)

Testing REST APIs with static data is fine in development, but what happens if this server was running in an environment where you have no control over the data you’re testing against? We would have no control over authentication; we would also not be able to test the GET /items/:id endpoint because we would not be certain about the ID returned by the server. Running the previous command again would result in the test failing:

$ upssert static-test.json
Executing 1 test suites (1 assertions)…
Create new item
✖ POST /items
1) POST /items
Error: expected '/items/2' to match /^\/items\/1$/ (body.id)
0 passing (38ms)
1 failing

Since we never reset the datastore, and we’re asserting that the ID returned should be /items/1, all future tests will fail.

To mitigate these issues, Upssert passes response bodies from previous test cases through to any test case which requires them using the requires: [...] property. Therefore, we can create a test suite which authenticates a user, passes the token received from the server to the next test to create an item and tests that the GET /items/:id endpoint returns the correct data:

Upssert supports Mustache templates, and properties are compiled against an object containing environment variables and previous test response bodies. In this case:

Test cases with dependencies have to require the data through the requires property. This enables Upssert to fail tests automatically if one or more of their dependencies have failed. If a test case id is not specified, it will be generated; ids generated as testN where N is the 1-based position of the test in the suite.

In the test suite above, we are not explicitly defining assertions; we’re passing string values directly to properties. Upssert converts these properties to a match assertion, and the value becomes a regular expression. For example, "body.name": "foobar" becomes

If you start or end the value with ^ or $ respectively, Upssert will not add any additional characters, it will use the expression as is. match assertions are not modified and are passed through as a regular expression.

We can run our test suite:

$ upssert dynamic-test.json
Executing 1 test suites (4 assertions)…
Get an item
✓ Authenticate
✓ Create
✓ Retrieve
3 passing (47ms)

We have successfully authenticated that our API is operational using test case dependencies and variables.

--

--