В настоящее время я учусь тестировать модули узлов. В последние дни я задал пару вопросов здесь, в StackOverflow, о том, как имитировать модули узлов, чтобы протестировать, например, что происходит в предложении .then () обещания. У меня есть отличные предложения от сообщества о том, как с этим справиться, и я зашел довольно далеко. Однако есть кое-что, что я не могу понять, и это связано с асинхронными вызовами.
Например, в настоящее время у меня есть следующий код для добавления сообщения:
const makeRequestStructure = require('./modules/makeRequestStructure.js').makeRequestStructure
const normalizeFinalResponse = require('./modules/normalizeFinalResponse.js').normalizeFinalResponse
const doARequest = require('./modules/doARequest.js').doARequest
exports.addPost = (event) => {
const requestStructure = makeRequestStructure('POST', '/posts')
const requestPostData = {
title: event.body.title,
content: event.body.content
}
return doARequest(requestStructure, requestPostData).then((res) => {
const finalResponse = normalizeFinalResponse(200, res)
return finalResponse
}).catch((err) => {
const finalResponse = normalizeFinalResponse(400, err)
return finalResponse
})
}
Вспомогательные функции, необходимые для запуска этого файла:
makeRequestStructure.js (находится по адресу ./modules/makeRequestStructure.js)
require('dotenv').config()
const { HOST, PORT } = process.env
module.exports.makeRequestStructure = function (method, path) {
return {
host: HOST,
port: PORT,
method: method,
path: path
}
}
Этот модуль использует переменные среды, те, которые я настроил в моем файле .env:
HOST=jsonplaceholder.typicode.com
POST=433
Далее у меня есть файл normalizeFinalResponse.js и файлы doARequest.js:
normalizeFinalResponse.js (находится по адресу ./modules/normalizeFinalResponse.js)
module.exports.normalizeFinalResponse = function (statusCode, message) {
return {
'statusCode': statusCode,
'body': { message: message }
}
}
doARequest.js (находится по адресу ./modules/doARequest.js)
const https = require('https')
module.exports.doARequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
Теперь этот код довольно прост. Запустив следующий файл, он выполнит POST-вызов jsonplaceholder.typicode.com:433/posts, содержащий в своем теле { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } }
const addPost = require('./addPost.js').addPost;
const event = { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } }
addPost(event).then((res) => {
console.info(res);
}).catch((err) => {
console.info(err);
});
В модуле addPost функция normalizeFinalResponse вызывается для нормализации ответа от api jsonplaceholder. Чтобы проверить это, я создал следующий тестовый файл.
//Dependencies
const mock = require('mock-require')
const sinon = require('sinon')
const expect = require('chai').expect
//Helper modules
const normalizeFinalResponse = require('../modules/normalizeFinalResponse.js')
const doARequest = require('../modules/doARequest.js')
//Module to test
const addPost = require('../addPost.js')
//Mocks
const addPostReturnMock = { id: 101 }
describe('the addPost API call', () => {
it('Calls the necessary methods', () => {
console.info(1)
//Mock doARequest so that it returns a promise with fake data.
//This seems to be running async. The test file continues to run when its not resolved yet
mock('../modules/doARequest', { doARequest: function() {
console.info(2)
return Promise.resolve(addPostReturnMock);
}});
console.info(3)
//Stub functions expected to be called
let normalizeFinalResponseShouldBeCalled = sinon.spy(normalizeFinalResponse, 'normalizeFinalResponse');
//Set a fake eventBody
let event = { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } }
//Call the method we want to test and run assertions
return addPost.addPost(event).then((res) => {
expect(res.statusCode).to.eql(200);
sinon.assert.calledOnce(normalizeFinalResponseShouldBeCalled);
})
});
});
Запуск этого тестового файла не соответствует утверждению, поскольку, очевидно, функция normalizeFinalResponse никогда не вызывается. Когда я использую console.info, они печатаются в порядке 1,3,2.
Это наводит меня на мысль, что функция mock() еще не завершена, и поэтому она действительно вызовет api jsonplaceholder. Но чем все-таки должна была быть вызвана функция normalizeFinalResponse, не так ли?
У меня такое чувство, что я не замечаю чего-то, что находится прямо перед моими глазами. Однако я не могу понять, что это такое. Если вы знаете, что не так с моим тестом, я хотел бы это услышать. Это помогает мне лучше понять написание тестов такого типа.
Мой package.json для справки:
{
"name": "mock-requests-tests",
"version": "0.0.1",
"description": "A test repository so i can learn how to mock requests",
"scripts": {
"test": "mocha --recursive tests/",
"test:watch": "mocha --recursive --watch tests/"
},
"devDependencies": {
"chai": "^4.1.2",
"mock-require": "^3.0.2",
"sinon": "^7.2.2"
}
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Причина, по которой шпион normalizeFinalResponse никогда не вызывается, заключается в том, что addPost вызывает метод напрямую, а не использует шпион, созданный локально.
Когда вы вызываете sinon.spy(), он создает сквозную функцию, которая отслеживает, был ли выполнен этот новый метод. Это сделано специально, поскольку вы не хотите, чтобы код постоянно менял ваши функции из-под вас.
Вообще говоря, вам было бы все равно, если бы normalizefinalResposne вообще запустился. Единственное, что вас должно беспокоить, это то, что при вводе addPost(x) он возвращает y, внутренние детали не имеют значения, пока для ввода x вы получите результат y. Это также упрощает рефакторинг в дальнейшем, поскольку ваш модульный тест сломается только в том случае, если функциональность перестанет работать должным образом, а не только потому, что код выглядит иначе, а функциональность останется прежней.
Из этого правила есть некоторые исключения, в основном при использовании стороннего кода. Один из таких примеров находится в doARequest. Вы можете использовать фиктивную библиотеку, чтобы переопределить модуль http, чтобы он возвращал созданные ответы и не выполнял сетевые запросы в реальном времени.