API — это сердце современного веб-приложения. Это все для того, чтобы его было легко потреблять, масштабировать и убедиться, что он работает как положено. В настоящее время я придерживаюсь принципа «все методы открытого API должны иметь тесты» (AOAMMHT). Раньше я работал с технологиями .NET, где тестирование API было связано с вызовом методов соответствующего объекта контроллера, который обычно представлял собой модульное тестирование — макетирование всех зависимостей контроллера, настройка ожидаемых возвращаемых значений.
Я передумал на тестировании с разработкой Node.js / Express.js. Для API я предпочитаю «сквозное» тестирование: настройка учетной записи пользователя, аутентификация, HTTP-вызовы к серверу, реальные вызовы в БД и возврат полезной нагрузки JSON. API должны быть протестированы с точки зрения потребителей, чтобы иметь возможность получить значимые результаты.
Инструменты и рамки
Довольно стандартная настройка: мокко , чай , запрос .
Mocha — проверенный временем инструмент для тестирования приложений Node.js, Chai — достаточно хорошая среда ожидания и Request как один из лучших HTTP-клиентов, с которыми я когда-либо работал.
Подготовить заявку к тестированию
Вышеуказанные зависимости устанавливаются через npm install
и должны быть сохранены в package.json
файл (с --save
опцией). В вашей структуре проекта должна быть test
папка внутри, которую мокко использует по умолчанию для просмотра тестов внутри. Там нужно добавить несколько файлов, у меня есть текущая структура, которая работает.
/test /specs auth.spec.js ... common.js mocha.opts utils.js runMocha.js
/api
Папка — это та, которая будет содержать спецификации для вашего API, mocha.opts
— содержит глобальную конфигурацию mocha, common.js
является общим файлом require, который используют все тесты, utils.js
— test helper, который будет содержать все необходимое во время тестирования, runMocha.js
утилиту, которая будет точкой входа для тестов.
mocha.opts
--require ./test/common.js --reporter spec --ui bdd --recursive --colors --timeout 60000 --slow 300
Опции Mocha позволяют требовать некоторый дополнительный файл javascript, а также настраивать глобальные настройки Mocha, какой репортер использовать, время ожидания и т. Д.
common.js
global.chai = require('chai'); global.expect = global.chai.expect;
Чтобы не требовать chai
в каждом файле спецификаций, можно потребовать его один раз и поместить в глобальную область видимости.
runMocha.js
process.env.NODE_ENV = process.env.NODE_ENV || 'test'; process.env.TEST_ENV = process.env.TEST_ENV || 'test'; var exit = process.exit; process.exit = function (code) { setTimeout(function () { exit(); }, 200); }; require('../source/server'); require('../node_modules/mocha/bin/_mocha');
Секретный соус — последние 2 строки. Поскольку require
синхронно, мы сначала «вызываем» API-сервер, чтобы встать, а после этого «вызываем» механизм мокко, чтобы начать тестирование. Таким образом, внутри каждого теста мы можем выполнять реальные HTTP-вызовы на реальных HTTP-серверах. Никаких издевательств.
package.json
// ... "scripts": { "test": "node test/runMocha", }, // ...
Файл пакета должен содержать скрипт для вызова тестов API с помощью простой npm test
команды.
Тест управляемый API
Mocha использует BDD (основанный на поведении) подход к тестированию. По сравнению с классическим TDD, BDD поощряет написание спецификаций на простом английском языке, что хорошо работает, особенно когда вы только начинаете с определенной функции.
Обычно я просто записываю спецификацию API, не задумываясь о том, как ее реализовать. Приведите пример того, с чем я недавно работал.
var request = require('request'); var testUtils = require('../utils'); describe('collections.spec.js', function () { describe('when non authorized', function () { it ('should not be authorized', function () { }); }); describe('when authorized', function () { describe('when new collection created', function () { describe('public', function () { it('should respond with 201 (created)', function () { }); it('should create new collection', function () { }); it('should have user', function () { }); it('should collection be public', function () { }); describe('and title is missing', function () { it('should respond with 412 (bad request)', function () { }); }); describe('with description', function () { it('should respond with 201 (created)', function () { }); it('should create new collection', function () { }); }); }); }); // etc.. }); });
Это своего рода скелет, который я должен иметь перед началом чего-либо еще.
Не авторизованный доступ
Если ваш API или его часть требует авторизации, я предпочитаю протестировать его.
var token, user, url, headers, response, results; beforeEach(function () { url = testUtils.getRootUrl() + '/api/collections'; }); describe('when non authorized', function () { beforeEach(function (done) { request.get({url: url, json: true}, function (err, resp, body) { response = resp; done(err); }); }); it ('should not be authorized', function () { expect(response.statusCode).to.equal(401); }); });
testUtils.getRootUrl()
возвращает квалифицированный URL для API, в зависимости от среды тестирования. Во время разработки, это именно то, http://localhost:3000
где вы server.js
начали.
Авторизованный доступ
Авторизованный доступ обычно требует какого-либо вида access_token
отправки по заголовкам или по строке запроса. Как бы то ни было, но utils.js
должен иметь метод, который бы создавал нового пользователя и получал токен доступа из API. Реальная реализация такого метода будет зависеть от вашего механизма аутентификации API.
Все тесты, требующие авторизации доступа, должны иметь такие beforeEach()
,
beforeEach(function (done) { testUtils.createTestUserAndLoginToApi(function (err, createdUser, accessToken) { token = accessToken; user = createdUser; headers = {'X-Access-Token': accessToken}; done(err); }); });
После получения access_token
он может использоваться как часть любых авторизованных вызовов.
Поведенческие тесты
Теперь все готово для тестирования поведения API. Ничего особенного, просто действуй так, как делают клиенты. Отправляйте HTTP-запросы, получайте ответы и проверяйте HTTP-статусы. Я просто выложу некоторый код, чтобы он дал вам направление.
describe('when new collection created', function () { describe('public', function () { beforeEach(function () { collection = {title: 'This is test collection', public: true}; }); beforeEach(function (done) { request.post({url: url, headers: headers, body: collection, json: true}, function (err, resp, body) { response = resp; results = body; done(err); }); }); it('should respond with 201 (created)', function () { expect(response.statusCode).to.equal(201); }); it('should create new collection', function () { expect(results.title).to.be.ok; expect(results._id).to.be.ok; }); it('should have user', function () { expect(results.user).to.equal(user.email); }); it('should collection be public', function () { expect(results.public).to.equal(true); }); describe('and title is missing', function () { beforeEach(function (done) { request.post({url: url, headers: headers, body: {}, json: true}, function (err, resp, body) { response = resp; results = body; done(err); }); }); it('should respond with 412 (bad request)', function () { expect(response.statusCode).to.equal(412); }); }); describe('with description', function () { beforeEach(function () { collection = {title: 'This is test collection', description: 'description'}; }); beforeEach(function (done) { request.post({url: url, headers: headers, body: collection, json: true}, function (err, resp, body) { response = resp; results = body; done(err); }); }); it('should respond with 201 (created)', function () { expect(response.statusCode).to.equal(201); }); it('should create new collection', function () { expect(results.description).to.equal('description'); }); }); }); });
Выводы
Я думаю, что динамические языки, такие как JavaScript, отлично подходят для тестирования API. Отсутствие типов исключает классы «модель-на-ответ», request.js
отлично подходит для выполнения HTTP-вызовов и mocha
делает вывод спецификаций привлекательным. Таким образом, несмотря на это, вы можете попробовать использовать этот подход и посмотреть, как он работает для вас.
Настройка тестов и запуск API-сервисов настолько легки в Node.js, что делает разработку API-тестов в первую очередь приятной и приятной вещью.