Автор оригинала: Frantz Vallie.
Строительство API на отдых с Nodejs чрезвычайно удивительна. Использование тестового подхода для разработки для разработки этих API значительно улучшит качество вашего кода и главное, что у вас есть уверенность в API. В этом уроке мы собираемся узнать, как построить устойчивые API на отдых с Awesome Adonisjs Framework, следуя практикам развития, ориентированной на тест.
Для практического проекта мы построим API отдыха для системы аутентификации JWT. Пользователи могут регистрироваться и войти в приложение для генерации JWT.
Предпосылки
Чтобы следовать, вам нужно будет установить Adonis CLI на вашем локальном компьютере. Если у вас его не установлено, вы можете запустить команду NPM I -G @ adonisjs/cli
установить его.
Во-первых, мы создадим совершенно новый проект Adonis, используя CLI. Запустите следующую команду: Adonis Новые счета-менеджер --api-только
После того, как проект генерируется, следующим шагом является установка некоторых зависимостей, которые нам нужны для подключения к базе данных и для тестирования. Запустите следующую команду для установки ADONIS VOW, которая является встроенным пакетом тестирования Adonis и SQLite3 для подключения к базе данных для запуска наших тестов.
adonis install @ adonisjs/vow sqlite3
Как только Adonis Vow установлен, он добавит vowfile.js
к руту проекта. В этом файле удалите комментарии со следующих строк кода:
const ace = require('@adonisjs/ace') await ace.call('migration:run', {}, { silent: true }) await ace.call('migration:reset', {}, { silent: true })
Эти линии выключаются в Code Code Code Test Runner запускают миграции перед запуском тестов и сбрасывают их после завершения всех испытаний. Запуск тестов сейчас с Тест Адониса
Команда должна пройти.
Шаг 2 – Написание нашего первого теста
Первый тест, который мы будем писать, предназначено для конечной точки регистрации. Пользователь должен быть в состоянии зарегистрироваться с новой учетной записью. Создайте новый функциональный тест с CLI, используя следующую команду:
adonis make:test RegisterUser
Мы добавим наш первый тест здесь, который описывает процесс регистрации для нового пользователя.
'use strict' const Factory = use('Factory') const User = use('App/Models/User') const { test, trait } = use('Test/Suite')('Register User') trait('Test/ApiClient') test('registers a new user and generates a jwt', async ({ assert, client }) => { // generate a fake user const { username, email, password } = await Factory.model('App/Models/User').make() // make api request to register a new user const response = await client.post('/api/register').send({ username, email, password }).end() // expect the status code to be 200 response.assertStatus(200) // assert the email and username are in the response body response.assertJSONSubset({ user: { email, username } }) // assert the token was in request assert.isDefined(response.body.token) // assert the user was actually saved in the database await User.query().where({ email }).firstOrFail() })
Утверждения, которые мы пишем, очень важны, чтобы убедиться, что функция, которую мы тестируем, правильно реализована. В этом случае мы уверены, что ответ – это успешный, токен определяется в ответе, и, главное, мы запускаем запрос в конце нашего теста, чтобы убедиться, что пользователь на самом деле сохраняется в базе данных. Мы не используем утверждение, но мы используем ForeorFail
Функция, и эта функция бросит ошибку, если пользователь с этим электронным письмом не найден, вызывая провал теста. Запуск тестового набора прямо сейчас дает следующий результат:
Наш тест терпит неудачу, конечно. Что важно для нас – это сообщение об ошибке, и это сообщение является руководством для нашего следующего шага. Мы получили 404
Это означает конечную точку, которую мы пытаемся получить доступ, не существует. Чтобы исправить эту ошибку, давайте зарегистрируем этот маршрут в файле маршрутов приложений.
// register a route for registration Route.group(() => { Route.post('register', 'RegisterController.store') }).prefix('api')
Пока мы на этом, давайте создадим контроллер для этого маршрута и создать магазин
метод.
adonis make:controller RegisterController
Добавьте магазин
Метод класса контроллера:
'use strict' class RegisterController { async store () {} } module.exports = RegisterController
Запуск наших тестов на этом этапе дает следующий вывод.
Это означает, что наш сервер отвечает на 204
Код статуса, который не то, что мы утверждаем. А 204
означает, что сервер ответил без содержимого. Нам нужно исправить это, фактически реализуя функциональные возможности, которые мы желаем:
'use strict' const User = use('App/Models/User') class RegisterController { async store({ auth, request, response }) { // get the user data from the request const { username, email, password } = request.all() const user = await User.create({ username, email, password }) // generate the jwt for the user const token = await auth.generate(user) return response.ok({ user, token }) } } module.exports = RegisterController
Запуск наших тестов теперь должен пройти.
Предотвращение вечнозеленых тестов
При написании тестов для вашего приложения всегда приятно убедиться, что ваши тесты не являются вечнозелеными, что означает тесты, которые не потерпят неудачу. Эти типы испытаний могут возникнуть, потому что утверждения не работают, или мы утверждаем против неправильной вещи. Чтобы убедиться, что ваши тесты не являются вечнозелеными, измените утверждение и посмотрите, не удается ли он. Для текущего теста я изменим следующую строку кода:
// before response.assertStatus(200) // after response.assertStatus(403) // before assert.isDefined(response.body.token) // after assert.isUndefined(response.body.token)
Я бегу свои тесты и посмотрите, как их не удалось. Если они этого не сделают, то есть проблема. После того, как я подтвердил, что мои утверждения на самом деле утверждают, что я возвращаю свои тесты обратно в исходное состояние, снова запустите тесты и вернитесь к зеленому.
Шаг 3 – Тестирование для дубликатов электронных писем
Давайте добавим тест, чтобы убедиться, что наше приложение отвечает соответствующему сообщению об ошибке, если письмо уже сделано. Добавьте следующий тест на Тест/Функциональный/Регистрационный пользователь .spec.js
файл:
test('returns an error if user already exists', async ({ assert, client }) => { // create a new user const { username, email, password } = await Factory.model('App/Models/User').create() const response = await client.post('/api/register').send({ username, email, password }).end() // assert the status code is 422 response.assertStatus(422) // get the errors from the response const { errors } = response.body // assert the error for taken email was returned assert.equal(errors[0].message, 'The email has already been taken.') })
Запуск наших тестов теперь дает нам ошибку сервера 500.
Теперь это не очень полезно, потому что 500
может быть что угодно. При практике TDD наш следующий шаг определяется большую часть времени по ошибке, которую мы получаем, когда мы проводим наш тест, но как мы продолжим, когда мы не знаем, что такое ошибка? Чтобы отменить, какую ошибку идет с нашего сервера, давайте изменим обработчик ошибок в Adonisjs, чтобы распечатать ошибку к консоли для нас, чтобы увидеть. Сначала нам нужно создать обработчик ошибок путем работы:
adonis make:ehandler
Это порождает класс под названием ExceptionHandler
В Приложение/Исключения/Handler.js
файл. ручка
Метод в этом классе обрабатывает все ошибки, исходящие из нашего приложения. Изменить это так:
... async handle (error, { request, response }) { console.log(error) response.status(error.status).send(error.message) } ...
Теперь, когда возникает ошибка на нашем сервере, она будет напечатана на консоли, прежде чем она отображается в качестве ответа.
Запуск нашего теста теперь дает нам четкую ошибку, с которой мы можем работать:
{ [Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: users.email] errno: 19, code: 'SQLITE_CONSTRAINT', status: 500 }
Наша база данных уже устанавливает электронную почту как уникальную, и база данных бросает ошибку, если она уже зарегистрирована. Мы будем использовать @ Adonisjs/Validator
Пакет, чтобы легко проверить данные перед сохранением в нашей базе данных. Во-первых, установите Validator:
adonis install @adonisjs/validator
После регистрации поставщика Validator давайте реализуем код для проверки электронной почты уникален. Мы изменим магазин
Метод в Registercontroller
:
async store ({ auth, request, response }) { // get the user data from request const { username, email, password } = request.all() // create a validator with validation rules const validation = await validate({ email }, { email: 'unique:users' }, { unique: 'The email has already been taken.' }) // check if validation fails if(validation.fails()) { return response.status(422).json({ errors: validation.messages() }) } // create user const user = await User.create({ username, email, password }) // create token for user const token = await auth.generate(user) // return response with newly created user and token return response.ok({ user, token }) } }
Этот метод создает новый валидатор, добавляет правило, чтобы убедиться, что электронная почта уникальна на Пользователи
Таблица А также добавляет пользовательскую ошибку, чтобы соответствовать тому, что мы утверждали в нашем тесте. Запуск нашего теста на данный момент тоже должен пройти.
Шаг 4 – Тестирование на требуемое имя пользователя
Давайте добавим тест, чтобы убедиться, что Имя пользователя
требуется для регистрации.
test('returns an error if username is not provided', async ({ assert, client }) => { // make a post request to register a user without the username const response = await client.post('/api/register').send({ username: null, email: 'test@email.com', password: 'password' }).end() // assert the status code is 422 response.assertStatus(422) // get the errors from the response body const { errors } = response.body // assert the error has one for required username assert.equal(errors[0].message, 'The username is required.') })
Запуск этого теста теперь бросает следующую ошибку:
{ [Error: SQLITE_CONSTRAINT: NOT NULL constraint failed: users.username] errno: 19, code: 'SQLITE_CONSTRAINT', status: 500 }
Чтобы предотвратить эту ошибку, нам нужно проверить, чтобы убедиться, что Имя пользователя
Требуется в контроллере, прежде чем фактически сохранить пользователя в базу данных. Давайте изменим валидатор, как это:
// create a new validator const validation = await validate({ username, email }, { email: 'unique:users', username: 'required' }, { required: 'The {{ field }} is required.', unique: 'The email has already been taken.' })
Запуск наших тестов теперь должен пройти.
Наконец, давайте добавим для функциональности входа в систему. Когда пользователь предоставляет свой адрес электронной почты и пароль, для них должен быть сгенерирован JWT и отправлен в виде ответа JSON. Давайте создадим функциональный тестовый набор для функциональности входа в систему.
adonis make:test LoginUser
Затем добавим этот тест на вновь сгенерированный файл:
'use strict' const Factory = use('Factory') const User = use('App/Models/User') const { test, trait } = use('Test/Suite')('Register User') trait('Test/ApiClient') test('a JWT is generated for a logged in user', async ({ assert, client }) => { // generate a fake user const { username, email, password } = await Factory.model('App/Models/User').make() // save the fake user to the database await User.create({ username, email, password }) // make api request to login the user const response = await client.post('api/login').send({ email, password }).end() // assert the status is 200 response.assertStatus(200) // assert the token is in the response assert.isDefined(response.body.token.type) assert.isDefined(response.body.token.token) })
Запуск этого теста должен потерпеть неудачу, и поскольку мы изменили наш обработчик ошибок, у нас есть четкая ошибка, с которой мы можем работать:
HttpException: E_ROUTE_NOT_FOUND: Route not found POST /api/login
Чтобы исправить эту ошибку, давайте зарегистрируем этот маршрут и создайте его связанный контроллер и метод. В файле маршрутов давайте добавим маршрут входа в систему.
Route.group(() => { Route.post('login', 'LoginController.generate') Route.post('register', 'RegisterController.store') }).prefix('api')
Далее мы генерируем контроллер:
adonis make:controller LoginController
'use strict' class LoginController { async generate() {} } module.exports = LoginController
Запуск тестов сейчас не удается, поэтому мы должны реализовать функциональность входа в систему для утверждений.
async generate ({ auth, request, response }) { // get user data from request const { email, password } = request.all() // attempt to login the user const token = await auth.attempt(email, password) return response.ok({ token }) }
Запуск тестов сейчас успешно.
Заключение
Надеюсь, вам теперь лучше понять, как создать API для отдыха после подхода TDD. Вот a Ссылка на исходный код Для этого учебника.