Node.js - как бороться с асинхронными макетами в тестовых файлах? - PullRequest
0 голосов
/ 07 января 2019

В настоящее время я учусь тестировать модули узлов. В последние дни я задал пару вопросов здесь, на 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.log(res);
}).catch((err) => {
  console.log(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.log(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.log(2)
      return Promise.resolve(addPostReturnMock);
    }});

    console.log(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.log, они печатаются в порядке 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"
  }
}

1 Ответ

0 голосов
/ 08 января 2019

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

При вызове sinon.spy() is создает сквозную функцию, которая отслеживает, был ли выполнен этот новый метод. Это сделано специально, так как вы не хотите, чтобы код постоянно менял ваши функции из-под вас.

Вообще говоря, вам было бы все равно, если бы normalizefinalResposne казнили вообще. Единственное, что вас должно беспокоить, это то, что при вводе addPost(x) он возвращает y, внутренние детали не имеют значения, пока для ввода x вы получите результат y. Это также упрощает рефакторинг позже, поскольку ваш модульный тест будет прерван только в том случае, если функциональность перестанет работать должным образом, а не только потому, что код выглядит иначе, в то время как функциональность остается прежней.

Есть некоторые исключения из этого правила, в основном при использовании стороннего кода. Одним из таких примеров является doARequest. Возможно, вы захотите использовать фиктивную библиотеку для переопределения модуля http, чтобы он возвращал специально сформированные ответы и не выполнял сетевые запросы в реальном времени.

...