Как я могу выполнить модульное тестирование функции, которая использует обещания и источники событий в 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 ответа

Решение

Я бы сказал, что вам нужно немного изменить код, чтобы сделать его более тестируемым.

Когда я пишу модульные тесты для функций, я имею в виду следующие моменты

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

  2. Всегда рефакторинг ваших функций, чтобы иметь очень конкретную ответственность.

Реализуя эти два в вашем примере, я бы отделил серверный вызов в сервисном модуле, единственная ответственность которого состоит в том, чтобы принимать URL-адреса (и конфигурации, если таковые имеются) для выполнения серверных вызовов.

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

  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 для каждого запроса.

Обновленный пример юнит-теста

Другие вопросы по тегам