Рубрики
Без рубрики

Как строить API на отдых с TDD и Adonis JS

Узнайте, как разрабатывать надежные узлы JS REST APIS, использующие Adonisjs и Test Delively

Автор оригинала: 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

Запуск наших тестов на этом этапе дает следующий вывод.

Регистрация пользовательского тестирования вывода 2

Это означает, что наш сервер отвечает на 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 Ссылка на исходный код Для этого учебника.