API CRUD Operations
The examples showcase the testing of CRUD operations on a REST API.
CRUD refers to the basic operations in a database: Create, Read, Update, and Delete. We can map these operations to HTTP methods in REST APIs:
- Create: HTTP
POST
operation to create a new resource. - Read: HTTP
GET
to retrieve a resource. - Update: HTTP
PUT
orPATCH
to change an existing resource. - Delete: HTTP
DELETE
to remove a resource.
This document has two examples, one that uses the core k6 APIs (k6/http
and checks
) and another to show the more recent APIs httpx
and k6chaijs
).
Test steps
In the setup() stage we create a user for the k6 HTTP REST API. We then retrieve and return a bearer token to authenticate the next CRUD requests.
The steps implemented in the VU stage are as follows:
- Create a new resource, a “croc”.
- Read the list of “crocs”.
- Update the name of the “croc” and read the “croc” to confirm the update operation.
- Delete the “croc” resource.
Core k6 APIs example
import http from 'k6/http';
import { check, group, fail } from 'k6';
export const options = {
vus: 1,
iterations: 1,
};
// Create a random string of given length
function randomString(length, charset = '') {
if (!charset) charset = 'abcdefghijklmnopqrstuvwxyz';
let res = '';
while (length--) res += charset[(Math.random() * charset.length) | 0];
return res;
}
const USERNAME = `${randomString(10)}@example.com`; // Set your own email or `${randomString(10)}@example.com`;
const PASSWORD = 'superCroc2019';
const BASE_URL = 'https://test-api.k6.io';
// Register a new user and retrieve authentication token for subsequent API requests
export function setup() {
const res = http.post(`${BASE_URL}/user/register/`, {
first_name: 'Crocodile',
last_name: 'Owner',
username: USERNAME,
password: PASSWORD,
});
check(res, { 'created user': (r) => r.status === 201 });
const loginRes = http.post(`${BASE_URL}/auth/token/login/`, {
username: USERNAME,
password: PASSWORD,
});
const authToken = loginRes.json('access');
check(authToken, { 'logged in successfully': () => authToken !== '' });
return authToken;
}
export default (authToken) => {
// set the authorization header on the session for the subsequent requests
const requestConfigWithTag = (tag) => ({
headers: {
Authorization: `Bearer ${authToken}`,
},
tags: Object.assign(
{},
{
name: 'PrivateCrocs',
},
tag
),
});
let URL = `${BASE_URL}/my/crocodiles/`;
group('01. Create a new crocodile', () => {
const payload = {
name: `Name ${randomString(10)}`,
sex: 'F',
date_of_birth: '2023-05-11',
};
const res = http.post(URL, payload, requestConfigWithTag({ name: 'Create' }));
if (check(res, { 'Croc created correctly': (r) => r.status === 201 })) {
URL = `${URL}${res.json('id')}/`;
} else {
console.log(`Unable to create a Croc ${res.status} ${res.body}`);
return;
}
});
group('02. Fetch private crocs', () => {
const res = http.get(`${BASE_URL}/my/crocodiles/`, requestConfigWithTag({ name: 'Fetch' }));
check(res, { 'retrieved crocs status': (r) => r.status === 200 });
check(res.json(), { 'retrieved crocs list': (r) => r.length > 0 });
});
group('03. Update the croc', () => {
const payload = { name: 'New name' };
const res = http.patch(URL, payload, requestConfigWithTag({ name: 'Update' }));
const isSuccessfulUpdate = check(res, {
'Update worked': () => res.status === 200,
'Updated name is correct': () => res.json('name') === 'New name',
});
if (!isSuccessfulUpdate) {
console.log(`Unable to update the croc ${res.status} ${res.body}`);
return;
}
});
group('04. Delete the croc', () => {
const delRes = http.del(URL, null, requestConfigWithTag({ name: 'Delete' }));
const isSuccessfulDelete = check(null, {
'Croc was deleted correctly': () => delRes.status === 204,
});
if (!isSuccessfulDelete) {
console.log(`Croc was not deleted properly`);
return;
}
});
};
httpx and k6chaijs example
import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js';
import {
randomIntBetween,
randomItem,
randomString,
} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export const options = {
// for the example, let's run only 1 VU with 1 iteration
vus: 1,
iterations: 1,
};
const USERNAME = `user${randomIntBetween(1, 100000)}@example.com`; // Set your own email;
const PASSWORD = 'superCroc2019';
const session = new Httpx({ baseURL: 'https://test-api.k6.io' });
// Register a new user and retrieve authentication token for subsequent API requests
export function setup() {
let authToken = null;
describe(`setup - create a test user ${USERNAME}`, () => {
const resp = session.post(`/user/register/`, {
first_name: 'Crocodile',
last_name: 'Owner',
username: USERNAME,
password: PASSWORD,
});
expect(resp.status, 'User create status').to.equal(201);
expect(resp, 'User create valid json response').to.have.validJsonBody();
});
describe(`setup - Authenticate the new user ${USERNAME}`, () => {
const resp = session.post(`/auth/token/login/`, {
username: USERNAME,
password: PASSWORD,
});
expect(resp.status, 'Authenticate status').to.equal(200);
expect(resp, 'Authenticate valid json response').to.have.validJsonBody();
authToken = resp.json('access');
expect(authToken, 'Authentication token').to.be.a('string');
});
return authToken;
}
export default function (authToken) {
// set the authorization header on the session for the subsequent requests
session.addHeader('Authorization', `Bearer ${authToken}`);
describe('01. Create a new crocodile', (t) => {
const payload = {
name: `Croc name ${randomString(10)}`,
sex: randomItem(['M', 'F']),
date_of_birth: '2023-05-11',
};
session.addTag('name', 'Create');
const resp = session.post(`/my/crocodiles/`, payload);
expect(resp.status, 'Croc creation status').to.equal(201);
expect(resp, 'Croc creation valid json response').to.have.validJsonBody();
session.newCrocId = resp.json('id');
});
describe('02. Fetch private crocs', (t) => {
session.clearTag('name');
const resp = session.get('/my/crocodiles/');
expect(resp.status, 'Fetch croc status').to.equal(200);
expect(resp, 'Fetch croc valid json response').to.have.validJsonBody();
expect(resp.json().length, 'Number of crocs').to.be.above(0);
});
describe('03. Update the croc', (t) => {
const payload = {
name: `New croc name ${randomString(10)}`,
};
const resp = session.patch(`/my/crocodiles/${session.newCrocId}/`, payload);
expect(resp.status, 'Croc patch status').to.equal(200);
expect(resp, 'Fetch croc valid json response').to.have.validJsonBody();
expect(resp.json('name'), 'Croc name').to.equal(payload.name);
// read "croc" again to verify the update worked
const resp1 = session.get(`/my/crocodiles/${session.newCrocId}/`);
expect(resp1.status, 'Croc fetch status').to.equal(200);
expect(resp1, 'Fetch croc valid json response').to.have.validJsonBody();
expect(resp1.json('name'), 'Croc name').to.equal(payload.name);
});
describe('04. Delete the croc', (t) => {
const resp = session.delete(`/my/crocodiles/${session.newCrocId}/`);
expect(resp.status, 'Croc delete status').to.equal(204);
});
}