В предыдущих частях мы обсуждали -

  • основы nodeJS
  • отладка
  • асинхронное программирование
  • стек вызовов и цикл событий
  • обещания
  • экспресс и развертывание

Часть 1 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-1-a3c1431f1e15

Часть 2 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-2-3035f8f46b09

Часть 3 - https://medium.com/@anujbaranwal/nodejs-from-scratch-part-3-20956ec252a3

Часть 4 - https://medium.com/@anujbaranwal/nodejs-from-scratch-part-4-d0aadf019c79

В этой части мы обсудим:

  • тестирование
  • мокко
  • библиотека утверждений - ожидайте
  • тестирование асинхронного кода и экспресс-приложений
  • супертест

Итак, приступим. Важно писать модульные тесты для кода, который вы пишете, чтобы убедиться, что новые функции, которые добавляются в ваше приложение, хорошо протестированы и не нарушают работу других функций, которые уже существуют в приложении.

Для приложений nodeJS настройка тестирования довольно проста. Мы будем использовать среду тестирования mocha для настройки наших тестов.

Мокко

Многофункциональная среда тестирования javascript, что означает наличие всех инструментов, необходимых для написания тестов, работающих на Node.js и в браузере, что делает асинхронное тестирование простым и увлекательным.

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

mkdir nodejs_tests

в nodejs_tests запустите npm init

in utils/utils.js

module.exports.add = (a, b) => a + b;

Чтобы установить мокко, запустите npm install --save-dev mocha

Это зависимость, которая нам пригодится при разработке приложения. Следовательно, его не нужно ставить вместе с кодом приложения.

in utils/utils.js

module.exports.add = (a, b) => {
    return a + b;
};

in utils/utils.test.js

const utils = require('./utils');
it('should add two values correctly', () => {
    const val = utils.add(3, 5);
    if (val !== 8) {
        throw new Error(`Value is not 8. It is ${val}.`); // if this gets executed, it will make the test to fail
    }
});

Когда мы запускаем utils.test.js с мокко, он автоматически вставляет it и describe в файл. it представляет собой конкретный пример модульного теста. Это функция, предоставляемая мокко. Он определяет новые тестовые случаи. Ожидается два параметра -

  • строка, описывающая тестовый пример
  • функция, которая проверяет, что что-то работает должным образом

Чтобы убедиться, что мокко захватывает этот файл, нам нужно обновить package.json -

"scripts": {
    "test": "mocha **/*.test.js" // recursively in all subdirectories - **
}

Однако каждый раз приходится запускать тесты вручную. Мы также можем настроить nodemon для отслеживания любых изменений и автоматического запуска тестов без нашего вмешательства.

nodemon --exec "npm test"

in package.json

"scripts":{
    "test": "mocha **/*.test.js",
    "test-watch": "nodemon --exec \"npm test\""
}

Запустите npm run test-watch, чтобы следить за любыми изменениями и непрерывно запускать тесты.

Библиотека утверждений

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

Доступно множество библиотек утверждений. Но тот, который мы будем использовать expect библиотеку



npm install --save-dev [email protected]

В последней версии произошли некоторые серьезные изменения.

С epxect тесты будут выглядеть так:

in utils/utils.test.js

const utils = require('./utils');
const expect = require('expect');
it('should add two values correctly', () => {
const val = utils.add(3, 5);
expect(val).toBe(8);
expect(typeof val).toBe('number');
});
it('should square the number correctly', () => {
const val = utils.square(3);
expect(val).toBe(9);
expect(typeof val).toBe('number');
});

Однако тестирование массивов и объектов с помощью toBe невозможно.

it('should assert array and object inclusion, equality and exclusion correctly', () => {
expect([1,2,3,4]).toInclude(1);
expect([1,2]).toEqual([1,2]);
expect({name: 'Anuj', age: 23}).toEqual({name: 'Anuj', age: 23});
expect({name: 'Anuj', age: 23}).toInclude({age: 23});
expect({name: 'Anuj', age: 23}).toExclude({name: 'Ankit'});
});

in utils/utils.js

module.exports.enterUserName = (user, fullName) => {
const names = fullName.split(' ');
user.firstName = names[0];
user.lastName = names[1];
return user;
};

in utils/utils.test.js

it('should fill the firstName and lastName correctly', () => {
const user = {location: 'Surat', age: 25};
const newUser = utils.enterUserName(user, 'Anuj Kumar');
expect(newUser).toInclude({firstName: 'Anuj'});
expect(newUser).toInclude({lastName: 'Kumar'});
});

Филиал - 11_Init

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

Тестирование асинхронного кода

in utils/utils.js

module.exports.asyncAdd = (a, b, callback) => {
setTimeout(() => {
callback(a + b);
}, 1000);
};

in utils/utils.test.js

it('should add asynchronously correctly', () => { // <- 1
utils.asyncAdd(3, 5, (sum) => {
expect(sum).toBe(9); // <- 2
});
}); // this always passes??

Причина, по которой тест всегда проходит, заключается в том, что перед выполнением обратного вызова (2) уже было возвращено (1). Таким образом, он отмечен как пройденный. Чтобы исправить это -

in utils/utils.test.js

it('should add asynchronously correctly', (done) => { // <- 1
utils.asyncAdd(3, 5, (sum) => {
expect(sum).toBe(9); // <- 2
done();
});
});

done сообщает mocha, что это асинхронная операция, и дождаться ее вызова, а затем считать тест завершенным. Следовательно, пока (2) не будет выполнено и не будет вызвано, он не будет завершен.

Тестирование экспресс-приложений

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

Создадим экспресс-сервер

in server/server.js

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Test');
})
app.listen(port, () => {
console.log(`Server listening on port: ${port}`);
});
module.exports.app = app;

Чтобы протестировать экспресс, мы воспользуемся модулем supertest.

npm install supertest --save-dev

in server/server.test.js

const request = require('supertest');
const expect = require('expect');
const app = require('./server').app;
it('should respond with the correct status and response', (done) => {
request(app)
.get('/')
.expect(200)
.expect('Test')
.end(done);
});
it('should respond with the 404 status and response as error: Page Not Found', (done) => {
request(app)
.get('/err')
.expect(404)
.expect((res) => {
expect(res.body).toInclude({error: 'Page Not Found!'}); // able to combine expect library with supertest
})
.end(done);
});

Филиал - 12_Init

Организация тестов с описанием

Если тесты запущены, вы увидите вывод на терминале -

В настоящее время все тесты вместе. Таким образом, не совсем ясно, как тесты связаны или сгруппированы определенным образом. Однако мы можем использовать describe для группировки тестовых примеров. Лучше будет читать и просматривать тестовые случаи в журналах

in utils/utils.test.js

const utils = require('./utils');
const expect = require('expect');
describe('Utils', () => {
describe('#Add', () => {
it('should add two values correctly', () => {
const val = utils.add(3, 5);
expect(val).toBe(8);
expect(typeof val).toBe('number');
});
});
describe('#Square', () => {
it('should square the number correctly', () => {
const val = utils.square(3);
expect(val).toBe(9);
expect(typeof val).toBe('number');
});
});
describe('Other utils', () => {
it('should assert array and object inclusion, equality and exclusion correctly', () => {
expect([1,2,3,4]).toInclude(1);
expect([1,2]).toEqual([1,2]);
expect({name: 'Anuj', age: 23}).toEqual({name: 'Anuj', age: 23});
expect({name: 'Anuj', age: 23}).toInclude({age: 23});
expect({name: 'Anuj', age: 23}).toExclude({name: 'Ankit'});
});
it('should fill the firstName and lastName correctly', () => {
const user = {location: 'Surat', age: 25};
const newUser = utils.enterUserName(user, 'Anuj Kumar');
expect(newUser).toInclude({firstName: 'Anuj'});
expect(newUser).toInclude({lastName: 'Kumar'});
});
});
describe('Async', () => {
it('should add asynchronously correctly', (done) => { // <- 1
utils.asyncAdd(3, 5, (sum) => {
expect(sum).toBe(8); // <- 2
done();
});
});
});
});

in server/server.test.js

const request = require('supertest');
const expect = require('expect');
const app = require('./server').app;
describe('Server', () => {
describe('GET /', () => {
it('should respond with the correct status and response', (done) => {
request(app)
.get('/')
.expect(200)
.expect('Test')
.end(done);
});
});
describe('GET /err', () => {
it('should respond with the 404 status and response as error: Page Not Found', (done) => {
request(app)
.get('/err')
.expect(404)
.expect((res) => {
expect(res.body).toInclude({error: 'Page Not Found!'});
})
.end(done);
});
});
});

На скриншоте выше хорошо видно, как сгруппированы тесты, и также легко увидеть их в журналах.

Тестовые шпионы

Иногда функция содержит вызовы функции из других модулей. В таких случаях нам нужно убедиться, что вызов другой функции был успешным, когда была вызвана наша функция. Однако на самом деле мы не хотим выполнять код другой функции. В таком случае нам нужно подделать другую функцию. Мы пользуемся шпионами.

in spies/app.js

const db = require('./db');
module.exports.signUp = (email, password) => {
db.saveUser({email, password});
};

in spies/db.js

module.exports.saveUser = (user) => {
console.log(`Saving user ${user}.`);
};

in spies/app.test.js

const expect = require('expect');
it('should call the method once', () => {
const spy = expect.createSpy(); // used when there is no function to spy on. tracks calls and arguments
spy();
expect(spy).toHaveBeenCalled();
});

Однако нам нужно проверить, что db.saveUser вызывается при вызове app.signUp.

Мы будем использовать rewire, который дает нам две важные вещи - __set__ и __get__ можно использовать для установки переменных из модуля. Посмотрим на практике -

npm install --save-dev rewire

in spies/app.test.js

const expect = require('expect');
const rewire = require('rewire');
const app = rewire('./app');
it('should call the method once', () => {
const spy = expect.createSpy();
spy();
expect(spy).toHaveBeenCalled();
});
it('should call db.saveUser when app.signUp is called', () => {
const db = {
saveUser: expect.createSpy()
};
app.__set__('db', db);
app.signUp();
expect(db.saveUser).toHaveBeenCalled();
});

Филиал - 13_Init

Это в значительной степени то, что мы рассмотрим в тестовых примерах для узлового приложения.

В этой части мы обсудили -

  • тестирование
  • мокко
  • библиотека утверждений - ожидайте
  • тестирование асинхронного кода
  • тестирование экспресс-приложения - супертест
  • шпионы

Спасибо. Увидимся в следующей части :)

Часть 6.1 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-1-api-development-5dee11785d62

Часть 6.2 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-2-api-development-f92f76eb3521

Часть 6.3 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-3-api-development-9b046fed7364

Часть 6.4 - https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-4-api-development-38d600a35ad9