Unit testing
In this guide we will see how you can run basic unit tests for a Strapi application using a testing framework.
Please note that this guide will not work if you are on Windows using the SQLite database due to how windows locks the SQLite file.
Install test toolsβ
Jest
contains a set of guidelines or rules used for creating and designing test cases - a combination of practices and tools that are designed to help testers test more efficiently.
Supertest
allows you to test all the api
routes as they were instances of http.Server.
sqlite3
is used to create an on-disk database that is created and deleted between tests.
- yarn
- npm
yarn add --dev jest supertest sqlite3
npm install jest supertest sqlite3 --save-dev
Once this is done add this to package.json
file
add test
command to scripts
section
"scripts": {
"develop": "strapi develop",
"start": "strapi start",
"build": "strapi build",
"strapi": "strapi",
"test": "jest --forceExit --detectOpenHandles"
},
and add those lines at the bottom of file
"jest": {
"testPathIgnorePatterns": [
"/node_modules/",
".tmp",
".cache"
],
"testEnvironment": "node"
}
Those will inform Jest
not to look for test inside the folder where it shouldn't.
Set up a testing environmentβ
Test framework must have a clean empty environment to perform valid test and also not to interfere with current database.
Once jest
is running it uses the test
environment (switching NODE_ENV
to test
)
so we need to create a special environment setting for this purpose.
Create a new config for test env ./config/env/test/database.js
and add the following value "filename": ".tmp/test.db"
β-βthe reason of that is that we want to have a separate sqlite database for tests, so our test will not touch real data.
This file will be temporary, each time test is finished, we will remove that file that every time tests are run on the clean database.
The whole file will look like this:
module.exports = ({ env }) => ({
connection: {
client: 'sqlite',
connection: {
filename: env('DATABASE_FILENAME', '.tmp/test.db'),
},
useNullAsDefault: true,
debug: false
},
});
Create a Strapi instanceβ
In order to test anything we need to have a strapi instance that runs in the testing environment, basically we want to get instance of strapi app as object, similar like creating an instance for process manager.
These tasks require adding some files - let's create a folder tests
where all the tests will be put and inside it, next to folder helpers
where main Strapi helper will be in file strapi.js.
const Strapi = require("@strapi/strapi");
const fs = require("fs");
let instance;
async function setupStrapi() {
if (!instance) {
await Strapi().load();
instance = strapi;
await instance.server.mount();
}
return instance;
}
async function cleanupStrapi() {
const dbSettings = strapi.config.get("database.connection");
//close server to release the db-file
await strapi.server.httpServer.close();
// close the connection to the database before deletion
await strapi.db.connection.destroy();
//delete test database after all tests have completed
if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {
const tmpDbFile = dbSettings.connection.filename;
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile);
}
}
}
module.exports = { setupStrapi, cleanupStrapi };
Test a Strapi instanceβ
We need a main entry file for our tests, one that will also test our helper file.
const fs = require('fs');
const { setupStrapi, cleanupStrapi } = require("./helpers/strapi");
beforeAll(async () => {
await setupStrapi();
});
afterAll(async () => {
await cleanupStrapi();
});
it("strapi is defined", () => {
expect(strapi).toBeDefined();
});
Actually this is all we need for writing unit tests. Just run yarn test
and see a result of your first test
yarn run v1.13.0
$ jest
PASS tests/app.test.js
β strapi is defined (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.187 s
Ran all test suites.
β¨ Done in 5.73s.
If you receive a timeout error for Jest, please add the following line right before the beforeAll
method in the app.test.js
file: jest.setTimeout(15000)
and adjust the milliseconds value as you need.
Test a basic endpoint controllerβ
In the example we'll use and example Hello world
/hello
endpoint from controllers section.
Some might say that API tests are not unit but limited integration tests, regardless of nomenclature, let's continue with testing first endpoint.
We'll test if our endpoint works properly and route /hello
does return Hello World
Let's create a separate test file where supertest
will be used to check if endpoint works as expected.
const request = require('supertest');
it("should return hello world", async () => {
await request(strapi.server.httpServer)
.get("/api/hello")
.expect(200) // Expect response http code 200
.then((data) => {
expect(data.text).toBe("Hello World!"); // expect the response text
});
});
Then include this code to ./tests/app.test.js
at the bottom of that file
require('./hello');
and run yarn test
which should return
β my-project yarn test
yarn run v1.13.0
$ jest --detectOpenHandles
PASS tests/app.test.js (5.742 s)
β strapi is defined (4 ms)
β should return hello world (208 ms)
[2020-05-22T14:37:38.018Z] debug GET /hello (58 ms) 200
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.635 s, estimated 7 s
Ran all test suites.
β¨ Done in 9.09s.
If you receive an error Jest has detected the following 1 open handles potentially keeping Jest from exiting
check jest
version as 26.6.3
works without an issue.
Test an auth
endpoint controllerβ
In this scenario we'll test authentication login endpoint with two tests
- Test
/auth/local
that should login user and returnjwt
token - Test
/users/me
that should return users data based onAuthorization
header
const request = require('supertest');
// user mock data
const mockUserData = {
username: "tester",
email: "tester@strapi.com",
provider: "local",
password: "1234abc",
confirmed: true,
blocked: null,
};
it("should login user and return jwt token", async () => {
/** Creates a new user and save it to the database */
await strapi.plugins["users-permissions"].services.user.add({
...mockUserData,
});
await request(strapi.server.httpServer) // app server is an instance of Class: http.Server
.post("/api/auth/local")
.set("accept", "application/json")
.set("Content-Type", "application/json")
.send({
identifier: mockUserData.email,
password: mockUserData.password,
})
.expect("Content-Type", /json/)
.expect(200)
.then((data) => {
expect(data.body.jwt).toBeDefined();
});
});
it('should return users data for authenticated user', async () => {
/** Gets the default user role */
const defaultRole = await strapi.query('plugin::users-permissions.role').findOne({}, []);
const role = defaultRole ? defaultRole.id : null;
/** Creates a new user an push to database */
const user = await strapi.plugins['users-permissions'].services.user.add({
...mockUserData,
username: 'tester2',
email: 'tester2@strapi.com',
role,
});
const jwt = strapi.plugins['users-permissions'].services.jwt.issue({
id: user.id,
});
await request(strapi.server.httpServer) // app server is an instance of Class: http.Server
.get('/api/users/me')
.set('accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Authorization', 'Bearer ' + jwt)
.expect('Content-Type', /json/)
.expect(200)
.then(data => {
expect(data.body).toBeDefined();
expect(data.body.id).toBe(user.id);
expect(data.body.username).toBe(user.username);
expect(data.body.email).toBe(user.email);
});
});
Then include this code to ./tests/app.test.js
at the bottom of that file
require('./user');
All the tests above should return an console output like
β my-project git:(master) yarn test
yarn run v1.13.0
$ jest --forceExit --detectOpenHandles
[2020-05-27T08:30:30.811Z] debug GET /hello (10 ms) 200
[2020-05-27T08:30:31.864Z] debug POST /auth/local (891 ms) 200
PASS tests/app.test.js (6.811 s)
β strapi is defined (3 ms)
β should return hello world (54 ms)
β should login user and return jwt token (1049 ms)
β should return users data for authenticated user (163 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 6.874 s, estimated 9 s
Ran all test suites.
β¨ Done in 8.40s.