Статьи

Тестирование API Node.js

Тесты важны; они обеспечивают защиту для ваших приложений или API. Как новички, можно забыть о необходимости писать тесты, которые охватывают важные части того, что вы создаете. Тем не менее, вы будете встречаться с ним, как вы делаете успехи в качестве разработчика.

В предыдущем уроке вы узнали, как создать API с помощью Node.js. Если вы не прошли через это, я предлагаю вам сделать это, прежде чем продолжить это. В этом руководстве вы будете писать тесты для API, созданного с использованием Node.js и Express. В конце этого руководства вы узнаете, как работает тестирование в Node.js, и сможете создавать функциональные и протестированные API.

Вы будете использовать Mocha, Expect и Supertest.

Mocha — тестовая среда JavaScript, которая делает асинхронное тестирование простым и увлекательным. Мокко поставляется с множеством замечательных функций, которые можно найти на сайте. Вы будете использовать их несколько.

Expect — это библиотека утверждений, которая позволяет легко делать более качественные утверждения. Вы увидите, как это работает. Supertest предоставляет высокоуровневую абстракцию для тестирования HTTP. Это необходимо, так как вы будете тестировать API.

Хватит разговоров, время написать код.

У меня есть проект списка дел, который уже создан для вас. Модель, файл конфигурации и часть app.js уже готовы для вас. Перейдите на GitHub и клонируйте репозиторий . Или вы можете просто сделать:

1
git clone https://github.com/izuchukwu1/node-todo-api.git

Ваш package.json должен выглядеть следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#package.json
 
{
  «name»: «node-todo-api»,
  «version»: «1.0.0»,
  «description»: «»,
  «main»: «server.js»,
  «scripts»: {
    «start»: «node server/server.js»,
    «test»: «export NODE_ENV=test || SET \»NODE_ENV=test\» && mocha server/**/*.test.js»,
    «test-watch»: «nodemon —exec ‘npm test'»
  },
  «engines»: {
    «node»: «8.0.0»
  },
  «keywords»: [],
  «author»: «»,
  «license»: «ISC»,
  «dependencies»: {
    «body-parser»: «^1.17.2»,
    «express»: «^4.15.3»,
    «lodash»: «^4.17.4»,
    «mongodb»: «^2.2.29»,
    «mongoose»: «^4.11.1»
  },
  «devDependencies»: {
    «expect»: «^1.20.2»,
    «mocha»: «^3.4.2»,
    «nodemon»: «^1.11.0»,
    «supertest»: «^3.0.0»
  }
}

Теперь запустите команду для установки зависимостей.

1
npm install

Для вашего теста создайте новую папку с именем test ; эта папка должна находиться в каталоге вашего сервера . Теперь создайте новый файл, в который вы напишите свой тест. Назовите файл server.test.js .

В этом файле начните с установки модулей, которые вы установили. Вам также необходимо указать файл вашего сервера и модель.

1
2
3
4
5
6
7
8
#server/test/server.test.js
 
const expect = require(‘expect’)
const request = require(‘supertest’)
const {ObjectId} = require(‘mongodb’)
 
const {app} = require(‘./../server’)
const {Todo} = require(‘./../models/todo’)

Вам нужно иметь несколько задач, которые будут использоваться во время тестов. Но эти задачи будут удаляться из тестовой базы данных при каждом запуске тестового набора. Чтобы справиться с этим, создайте два теста, как это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#server/test/server.test.js
 
const todos = [{
  _id: new ObjectId(),
  text: «First test todo»
}, {
  _id: new ObjectId(),
  text: «Second test todo»,
  completed: true,
  completedAt: 333
}]
 
beforeEach((done) => {
  Todo.remove({}).then(() => {
    return Todo.insertMany(todos)
  }).then(() => done())
})

Блок before очищает вашу базу данных Todo, а затем вставляет задачи, установленные выше. Это гарантирует, что у вас есть стабильное количество записей в вашей базе данных, чтобы ваши тесты не сталкивались с проблемами.

После этого вы можете написать тест для запроса POST . Для вашего POST- запроса вы напишите два теста. Первый сделает запрос на правильное задание и будет успешным. Второй сделает запрос с недействительным телом, и это не должно создавать новую задачу.

Вот как должен выглядеть тест.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#server/test/server.test.js
 
describe(‘POST /todos’, () => { // 1
  it(‘should create a new todo’, (done) => {
    let text = ‘Test todo text’ // 2
 
    request(app) // 3
      .post(‘/todos’)
      .send({text})
      .expect(200)
      .expect((res) => {
        expect(res.body.text).toBe(text)
      })
      .end((err, res) => { // 4
        if (err) {
          return done(err)
        }
 
        Todo.find({text}).then((todos) => { // 5
          expect(todos.length).toBe(1)
          expect(todos[0].text).toBe(text)
          done()
        }).catch((e) => done(e))
      })
  })
 
  it(‘should not create todo with invalid body data’, (done) => { // 6
 
    request(app) // 7
      .post(‘/todos’)
      .send({})
      .expect(400)
      .end((err, res) => {
        if (err) {
          return done(err)
        }
 
        Todo.find().then((todos) => { // 8
          expect(todos.length).toBe(2)
          done()
        }).catch((e) => done(e))
      })
  })
})

Запустите тест с помощью команды:

1
npm run test

Это должно потерпеть неудачу. Вот что происходит:

  1. Описывает тест.
  2. Создает новый список дел и сохраняет его как значение для текста.
  3. Вы делаете POST-запрос к пути / todos вашего API, отправляя задание, которое вы создали, как тело запроса. Вы ожидаете, что код состояния для запроса будет 200, а тело дела должно равняться значению text .
  4. Ошибка возвращается, если таковая имеется, и это приведет к завершению запроса. Таким образом, следующий блок кода не будет запущен. Если ошибки не обнаружены, процесс продолжается.
  5. В базу данных поступает запрос на поиск созданного дела. Вы ожидаете, что длина задач в базе данных будет равна 1, а текст задачи будет равен значению текста.
  6. Это тест, выполненный с неверными данными тела.
  7. POST- запрос делается к пути / todos . На этот раз вы отправляете запрос без тела. Вы ожидаете получить 400 запросов. Не нужно проверять тело, так как вы не отправляете. Если обнаружена ошибка, она возвращается и код останавливается.
  8. Если ошибки не обнаружены, в базу данных отправляется запрос на проверку длины задач. Мы ожидаем, что база данных будет содержать только 2, которые являются задачами, созданными в начале.

Чтобы пройти этот тест, перейдите в файл server.js и вставьте код, необходимый для запроса POST.

01
02
03
04
05
06
07
08
09
10
11
12
13
#server/server.js
 
app.post(‘/todos’, (req, res) => {
  let todo = new Todo({
    text: req.body.text
  })
 
  todo.save().then((doc) => {
    res.send(doc)
  }, (e) => {
    res.status(400).send(e)
  })
})

Это просто — тест должен возвращать длину задач, доступных в базе данных. Как вы уже знаете, длина задач должна быть 2. Почему? Вы угадали. В начале набора тестов вы создали блок beforeEach который очищает вашу базу данных и вставляет новые beforeEach каждом запуске набора тестов.

Чтобы проверить, что запрос на получение всех задач работает, вот код для этого.

01
02
03
04
05
06
07
08
09
10
11
12
13
#server/test/server.test.js
 
describe(‘GET /todos’, () => {
  it(‘should get all todos’, (done) => {
    request(app)
      .get(‘/todos’)
      .expect(200)
      .expect((res) => {
        expect(res.body.todos.length).toBe(2)
      })
      .end(done)
  })
})

Выше вы делаете GET- запрос к пути / todos . На этот раз вы не передаете ничего как тело запроса, потому что это запрос GET . Вы ожидаете получить ответ с кодом состояния 200 . Тогда вы ожидаете, что длина задач будет 2.

Когда вы запускаете тест, вы должны получить ошибку. Попробуйте заставить ошибку пройти самостоятельно.

Могу поспорить, ты получил это на работу. Вот код для прохождения теста; сравните это с вашим решением.

1
2
3
4
5
6
7
8
9
#server/server.js
 
app.get(‘/todos’, (req, res) => {
  Todo.find().then((todos) => {
    res.send({todos})
  }, (e) => {
    res.status(400).send(e)
  })
})

Когда к пути / todos сделан запрос GET , вы хотите найти все задачи в коллекции Todo и вернуть их как задачи. Добавление этого в server.js заставляет тест пройти.

Далее вы напишите три теста для запроса GET, сделанного для получения отдельных задач. Первый должен получить и вернуть задачу. Второе и третье должны возвращать ошибку 404 в тех случаях, когда задача не найдена.

Откройте ваш server.test.js и создайте новый блок описания с первым тестовым примером.

01
02
03
04
05
06
07
08
09
10
11
12
#server/server.test.js
 
describe(‘GET /todos/:id’, () => {
  it(‘should return todo doc’, (done) => {
    request(app)
      .get(`/todos/${todos[0]._id.toHexString()}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.todo.text).toBe(todos[0].text)
      })
      .end(done)
  })

Этот тест делает запрос GET для получения первой задачи, доступной в вашей базе данных. Вы ожидаете получить код состояния 200 , а затем убедитесь, что текстовое значение задачи совпадает с созданным.

Следующий тест выглядит следующим образом.

1
2
3
4
5
6
7
it(‘should return 404 if todo is not found’, (done) => {
   let _id = new ObjectId(‘5967989ee978311656e93a59’)
   request(app)
     .get(`/todos/${todos/_id.toHexString()}`)
     .expect(404)
     .end(done)
 })

Здесь вы ищете задачу, используя идентификатор, который не соответствует идентификатору любой задачи, сохраненной в вашей базе данных. Тест ожидает, что этот запрос вернет ошибку 404 .

Последний тест похож на первый, но немного отличается; вот как это выглядит.

1
2
3
4
5
6
7
8
it(‘should return 404 for non-object ids’, (done) => {
    let hexId = ‘5967989ee978311656e93a5312’
    request(app)
      .get(`/todos/${todos/hexId}`)
      .expect(404)
      .end(done)
  })
})

Здесь вы создаете неверный ObjectId и пытаетесь запросить базу данных, чтобы получить ObjectId соответствующую созданному ObjectId . Тест ожидает, что запрос вернет ошибку 404 .

Когда вы запускаете тест, все они должны потерпеть неудачу. Чтобы заставить их пройти, добавьте следующий код в ваш файл server.js .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#server/server.js
 
app.get(‘/todos/:id’, (req, res) => {
  let id = req.params.id // 1
    
  if (!ObjectId.isValid(id)) { // 2
    return res.status(404).send(‘ID is not valid’)
  }
 
  Todo.findById(id).then((todo) => {
    if (!todo) { // 3
      return res.status(404).send()
    }
    res.send({todo}) //4
  }).catch((e) => {
    res.status(400).send()
  })
})
  1. Получить идентификатор задачи, запрошенной из параметров.
  2. Проверьте, является ли идентификатор действительным. Если идентификатор недействителен, выдается сообщение об ошибке.
  3. Если идентификатор действителен, попробуйте найти задачу, которая соответствует этому идентификатору, с findById метода findById . Если с этим идентификатором не найдено дел, отправляется ошибка 404 .
  4. Если задача найдена, она отправляется. Затем вы перехватываете любую ошибку и отправляете ее.

Запустите тестовую команду еще раз, и она должна пройти.

Тест для вашего запроса DELETE будет немного похож на тест для вашего запроса GET.

Сначала вы хотите проверить, что задание удалено.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
#server/test/server.test.js
 
describe(‘DELETE /todos/:id’, () => {
  it(‘should delete a todo’, (done) => {
    let hexId = todos[0]._id.toHexString() // 1
    request(app)
      .delete(`/todos/${hexId}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.todo._id).toBe(hexId)
      })
      .end((err, res) => { // 2
        if (err) {
          return done(err)
        }
      })
 
      Todo.findById(hexId).then((todo) => { // 3
        expect(todo.hexId).toNotExist()
        done()
      }).catch((e) => done(e))
  })

Вот что происходит выше:

  1. Вы устанавливаете идентификатор задачи для переменной с именем hexId . Затем выполняется запрос DELETE на путь к заданию с использованием идентификатора. Вы ожидаете получить ответ 200 , а полученный список будет соответствовать значению hexId .
  2. Если обнаружена какая-либо ошибка, она возвращается.
  3. Если ошибки нет, тест идет дальше и запрашивает вашу базу данных, используя идентификатор, сохраненный в качестве значения hexId . Поскольку запрос DELETE был ранее отправлен, тест ожидает, что задача, соответствующая этому идентификатору, не существует.

Затем вы хотите проверить, что когда делается запрос на удаление несуществующего списка дел, в ответе содержится ошибка 404 . Это важно, так как невозможно дважды удалить задачу. Вот как должен выглядеть тест для этого.

1
2
3
4
5
6
7
8
9
#server/test/server.test.js
 
 it(‘should return 404 if todo is not found’, (done) => {
   let hexId = new ObjectId().toHexString()
   request(app)
     .delete(`/todos/${todos/hexId}`)
     .expect(404)
     .end(done)
 })

Это создает идентификатор для задачи, которая не существует в базе данных. Затем делается запрос DELETE, чтобы удалить задачу. Ожидается, что тест выдаст ошибку 404, поскольку список дел не существует.

Вы хотите проверить, что DELETE с использованием неверного идентификатора возвращает ошибку 404 , вот так.

1
2
3
4
5
6
7
8
9
#server/test/server.test.js
 
  it(‘should return 404 for non-object ids’, (done) => {
    request(app)
      .delete(‘/todos/123abc’)
      .expect(404)
      .end(done)
  })
})

Чтобы пройти тест, откройте ваш server.js и бросьте его.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#server/server.js
 
app.delete(‘/todos/:id’, (req, res) => {
  let id = req.params.id
 
  if (!ObjectId.isValid(id)) {
    return res.status(404).send()
  }
 
  Todo.findByIdAndRemove(id).then((todo) => {
    if (!todo) {
      return res.status(404).send()
    }
    res.send({todo})
  }).catch((e) => {
    res.status(400).send()
  })
})

Запустите команду для проверки:

1
npm run test

И ваши тесты должны пройти.

Теперь вы знаете, как настроить набор тестов при создании API с помощью Node.js. Вы использовали Mocha, Expect и Supertest для тестирования API. Одним из преимуществ этого является то, что вам не нужно всегда запускать Postman при создании вашего API. С вашим тестом вы можете узнать, что сломано.

Прежде чем закончить, обратите внимание, что JavaScript стал одним из де-факто языков работы в сети. Это не без кривых обучения, и есть множество фреймворков и библиотек, которые также могут вас занять. Если вы ищете дополнительные ресурсы для обучения или использования в своей работе, посмотрите, что у нас есть на Envato Market .

Используя то, что вы теперь знаете, вы хорошо знакомы с миром тестирования.