Тестирование¶
Тестирование Node.js приложений позволяет избегать регрессионных ошибок при добавлении нового функционала или при изменении уже существующего. Поскольку с помощью Node.js создаются именно серверные приложения, в данной статье будет рассмотрено только тестирование API.
Рассмотрим тестирование API на примере, приведенном в статье REST API. Только из app.js
необходимо экспортировать экземпляр созданного сервера.
app.js
module.exports = app
Экземпляр сервера, созданного с помощью Express, экспортируется для последующего его использования при написании тестов.
Для тестирования необходимо установить модули mocha
, chai
и chai-http
.
npm install mocha chai chai-http --save
Модуль mocha
предоставляет асинхронную среду для выполнения тестов и общие конструкции для их создания: describe
, it
и т. д. Модули chai
и chai-http
являются библиотеками утверждения по отношению к mocha
и предоставляют дополнительные функции для написания специализированных тестов, например, для проверки API.
Тесты для приведенного выше REST API.
test.js
process.env.NODE_ENV = 'test'
const chai = require('chai'),
chaiHttp = require('chai-http'),
expect = chai.expect
;(fs = require('fs')), (server = require('./app'))
chai.use(chaiHttp)
describe('Users API', () => {
before(() => {
const TEST_DATA = {
1: { id: 1, login: 'login1', password: 'password1' },
2: { id: 2, login: 'login2', password: 'password2' },
}
fs.writeFileSync(
'data-test.json',
JSON.stringify(TEST_DATA)
)
})
it('should get all users', (done) => {
chai
.request(server)
.get('/api/users')
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.haveOwnProperty('data')
done()
})
})
it('should get user by id', (done) => {
chai
.request(server)
.get('/api/users')
.query({ id: 2 })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.haveOwnProperty('data')
expect(res.body.data.id).to.equal(2)
done()
})
})
it("shouldn't get non-existing user", (done) => {
chai
.request(server)
.get('/api/users')
.query({ id: 4 })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(404)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it('should create user', (done) => {
chai
.request(server)
.post('/api/users')
.send({
user: {
id: 3,
login: 'login3',
password: 'password3',
},
})
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it("shouldn't create user with existing id", (done) => {
chai
.request(server)
.post('/api/users')
.send({
user: {
id: 3,
login: 'login3',
password: 'password3',
},
})
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(409)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it('should update user', (done) => {
chai
.request(server)
.put('/api/users')
.send({
user: {
id: 3,
login: 'login_3',
password: 'password_3',
},
})
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it("shouldn't update non-existing user", (done) => {
chai
.request(server)
.put('/api/users')
.send({
user: {
id: 4,
login: 'login_4',
password: 'password_4',
},
})
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(404)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it('should delete user', (done) => {
chai
.request(server)
.delete('/api/users')
.query({ id: 2 })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
it("shouldn't delete non-existing user", (done) => {
chai
.request(server)
.delete('/api/users')
.query({ id: 4 })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(404)
expect(res.body).to.haveOwnProperty('message')
done()
})
})
})
Результат в консоли должен быть таким.
Здесь переменной process.env.NODE_ENV
задается тестовое окружение, чтобы тестирование API происходило на файле data-test.json
и не затронуло реальных данных в data.json
.
Перед запуском тестов в блоке before()
в файл записываются исходные для тестов данные.
Запросы GET
, POST
, PUT
и DELETE
осуществляются с помощью одноименных методов, вызываемых по отношению к объекту, который возвращает метод request()
. Все эти методы принимают URL маршрута, на который должен быть выполнен запрос.
Для конфигурации запроса используются следующие методы:
set
- задает HTTP-заголовки;query
- устанавливает GET-параметры;send
- задает тело запроса (для POST- или PUT-запросов);attach
- позволяет прикреплять к запросу файлы.
Вызов request()
инициирует запуск сервера и после выполнения запроса автоматически вызывает метод close()
завершая его работу. Но в процессе тестирования скорее всего придется осуществлять более одного запроса и тогда запуск и остановка сервера для каждого запроса потребует больше вычислительных мощностей и займет больше времени. Чтобы избежать этого, используйте метод keepOn()
.
let requester = chai.request(app).keepOpen()
Promise.all([
requester.get('/api/users'),
requester.get('/api/users').query({ id: 2 }),
])
.then((responses) => {}) //массив ответов
.then(() => requester.close())
Использование keepOn()
не завершает работу сервера после выполнения запроса(ов), для этого необходимо самостоятельно вызвать метод close()
.
Для проверки корректности запроса используется функция expect()
модуля chai
, которая принимает два параметра: проверяемое значение и сообщение, которое будет выведено при неудачном выполнении теста (необязательный параметр).
Применительно к возвращаемому функцией expect()
значению используются специальные функции, задающие определенные условия для проверки переданного значения (equal
, not
, empty
и т. д.). С полным списком таких функций можно ознакомиться в официальной документации.
Конректно для HTTP-запросов модуль chai-http
предоставляет набор дополнительных функций, вызываемых после equal
, not
, empty
и т. д. и позволяющих проверять специфические для объектов запроса и ответа свойства.
Список функций chai-http:
header
- проверяет наличие определенного HTTP-заголовка;
expect(res).to.have.header('content-type', 'text/html')
headers
- проверяет наличие HTTP-заголовков в целом;
expect(res).to.have.headers
ip
- проверяет, является ли переданная строка IP-адресом;
expect('127.0.0.1').to.be.an.ip
json
/text
/html
- проверяет значение заголовка Content-type
;
expect(res).to.be.json
expect(res).to.be.text
expect(res).to.be.html
redirect
- проверяет соответствие кода ответа к одному из кодов редиректа;
expect(res).to.redirect
redirectTo
- проверяет, был ли редирект на определенный маршрут;
expect(res).to.redirectTo('http://redirect.com')
cookie
- проверяет наличие и значение cookie-файлов.
expect(req).to.have.cookie('token')
expect(req).to.have.cookie('token', 'abcdefg')