Как я могу выполнить модульное тестирование функции, которая использует обещания и источники событий в Node.js?
Мой вопрос о модульном тестировании с обещаниями и источниками событий в Node.js. Я использую рамки жасмина, если это имеет значение.
Приведенный ниже код использует модуль https из Node.js для отправки запроса в API. API вернет JSON. JSON из API - это переменная rawData в приведенном ниже коде.
Я хочу провести модульный тест, чтобы функция возвращала JSON (а не объект JavaScript).
Я безуспешно пробовал несколько подходов к модульному тестированию этого аспекта этой функции:
1) Я попытался шпионить за конструктором Promise, чтобы он возвращал поддельную функцию, которая просто возвращала бы строку JSON.
2) Я попытался шпионить за.on('eventType', callback) функцией EventEmitters в Node.js, чтобы подделать функцию, которая возвращает JSON.
Мой вопрос: возможен ли какой-либо из двух вышеуказанных подходов и / или рекомендуется ли для достижения моей цели? Существует ли другой подход к изоляции запроса http и отправке событий от цели моего модульного теста? Нужно ли переписывать эту функцию для облегчения модульного тестирования?
const https = require('https');
function getJSON() {
return new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
})
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
return json;
})
}
3 ответа
Я бы сказал, что вам нужно немного изменить код, чтобы сделать его более тестируемым.
Когда я пишу модульные тесты для функций, я имею в виду следующие моменты
Вам не нужно проверять встроенные или библиотечные модули, так как они уже хорошо протестированы.
Всегда рефакторинг ваших функций, чтобы иметь очень конкретную ответственность.
Реализуя эти два в вашем примере, я бы отделил серверный вызов в сервисном модуле, единственная ответственность которого состоит в том, чтобы принимать URL-адреса (и конфигурации, если таковые имеются) для выполнения серверных вызовов.
Теперь, когда вы делаете это, вы получаете два преимущества: 1. у вас есть многократно используемый фрагмент кода, который вы теперь можете использовать для других обращений к серверу (также делает ваш код чище и короче)
- Поскольку это модуль, теперь вы можете написать отдельные тесты для этого модуля и взять на себя ответственность за проверку того, выполняются ли серверные вызовы из вашего текущего модуля, который его использует.
Теперь все, что осталось проверить в вашей функции getJSON, - это подсмотреть этот сервисный модуль и использовать tohaveBeenCalledWith и проверить, что данные правильно проанализированы. Вы можете издеваться над сервисом, чтобы вернуть нужные данные.
1 это вызов службы, так что тестируйте toHaveBeenCalledWith
2 его разбор в JSON, поэтому тест на действительный / недействительный JSON и тест на сбои
//no need to test whether https is working properly
//its already tested
const https = require('https');
const service = require("./pathToservice");
function getJSON() {
return service.get(somConfig)
.then(json => {
JSON.parse(json);
return json;
})
}
//its cleaner now
//plus testable
Есть ли причина, по которой вы хотите придерживаться https
за оформление запроса? Если нет, ваш код и ваше тестирование могут стать действительно простыми. Я приведу пример с использованием axios.
Http-запрос может выглядеть так
getJSON() {
const url = 'https://httpbin.org/get';
return axios
.get(url)
.then(response => response);
}
и вы можете заглушить get
позвонить с Синона
lab.experiment('Fake http call', () => {
lab.before((done) => {
Sinon
.stub(axios, 'get')
.resolves({ data: { url: 'testUrl' } });
done();
});
lab.test('should return the fake data', (done) => {
const result = requestHelper.getJSON2();
result.then((response) => {
expect(response.data.url).to.eqls('testUrl');
axios.get.restore();
done();
});
});
});
С существующим кодом, nock
будет работать так
lab.experiment('Fake http call with nock', () => {
lab.test('should return the fake data', (done) => {
nock('https://httpbin.org')
.get('/get')
.reply(200, {
origin: '1.1.1.1',
url: 'http://testUrl',
});
const result = requestHelper.getJSON2();
result.then((response) => {
const result = JSON.parse(response);
console.log(JSON.parse(response).url);
expect(result.url).to.eqls('http://testUrl');
nock.cleanAll();
done();
});
});
});
Полный код здесь
Я думаю, что вы не добились успеха, потому что вы возвращаетесь прямо так. Это должно быть как:
function getJSON(callback) {
(new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
}))
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
callback(json);
})
}
// to use this:
getJSON((your_json)=> {
// handling your json here.
})
Вы можете использовать child_process, чтобы порождать тестовый сервер для предоставления JSON API. Пример:
const { spawn } = require('child_process');
const expect = chai.expect;
const env = Object.assign({}, process.env, { PORT: 5000 });
const child = spawn('node', ['test-api.js'], { env });
child.stdout.on('data', _ => {
// Make a request to our app
getJSON((foo)=>{
// your asserts go here.
expect(foo).to.be.a('object');
expect(foo.some_attribute).to.be.a('string')
// stop the server
child.kill();
});
});
Вы можете настроить свой someConfig
переменная в тестовой среде, указывающая на " http://127.0.0.1:5000/". Ваш файл test-api.js представляет собой простой скрипт nodejs, который всегда отвечает ожидаемым JSON для каждого запроса.
Обновленный пример юнит-теста