简体   繁体   English

Node.js-如何处理测试文件中的异步模拟?

[英]Node.js - how to deal with async mocks in test files?

Currently i am learning how to test node modules. 目前,我正在学习如何测试节点模块。 In the recent days i have asked a couple of questions here on StackOverflow about how to mock node modules in order to test, for example, what happens in the .then() clause of a promise. 最近几天,我在StackOverflow上问了几个有关如何模拟节点模块以测试的问题,例如,对promise的.then()子句进行了什么处理。 I got some great suggestions from the community on how to tackle this and i have come quite far. 我从社区中获得了一些有关如何解决此问题的好建议,并且我取得了很大的成就。 However there is still something that i can't wrap my mind around and it has to do with asynchronous calls. 但是,仍有一些我无法解决的问题,这与异步调用有关。

For example, i currently have the following code to add a post: 例如,我目前有以下代码来添加帖子:

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
  })
}

The helper functions that are required in order to run this file are: 运行此文件所需的辅助功能是:

makeRequestStructure.js (located at ./modules/makeRequestStructure.js ) 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
  }
}

This module uses environment variables, the ones that i have configured in my .env file are 该模块使用环境变量,我在.env文件中配置的是

HOST=jsonplaceholder.typicode.com
POST=433

Next i have the normalizeFinalResponse.js file and the doARequest.js files: 接下来,我有normalizeFinalResponse.js文件和doARequest.js文件:

normalizeFinalResponse.js (located at ./modules/normalizeFinalResponse.js ) normalizeFinalResponse.js (位于./modules/normalizeFinalResponse.js

module.exports.normalizeFinalResponse = function (statusCode, message) {
  return {
    'statusCode': statusCode,
    'body': { message: message }
  }
}

doARequest.js (located at ./modules/doARequest.js ) 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()
  })
}

Now, this code is pretty straightforward. 现在,此代码非常简单。 By running the following file it will make a POST call to jsonplaceholder.typicode.com:433/posts with in its body { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } } 通过运行以下文件,它将对jsonplaceholder.typicode.com:433/posts进行POST调用,其正文{ 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);
});

In the addPost module the function normalizeFinalResponse is called to normalize the response from the jsonplaceholder api. addPost模块中,调用函数normalizeFinalResponse来标准化来自jsonplaceholder api的响应。 In order to check this i have created the following test file. 为了检查这一点,我创建了以下测试文件。

//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);
    })
  });
});

Running this test file fails the assertion because apparently the normalizeFinalResponse function is never called. 运行该测试文件会使断言失败,因为显然从未调用过normalizeFinalResponse函数。 When i use the console.log's they print in the order 1,3,2. 当我使用console.log时,它们以1,3,2的顺序打印。 This leads me to belive that the mock() function is not finished yet and so it will make an actual call to the jsonplaceholder api. 这使我相信, mock()函数尚未完成,因此它将对jsonplaceholder api进行实际调用。 But than still the function normalizeFinalResponse should have been called, right? 但是比起仍然应该调用函数normalizeFinalResponse吧?

I have the feeling that i overlook something thats right in front of my eyes. 我有一种感觉,就是我忽略了眼前的事物。 However i can't figure out what it is. 但是我不知道这是什么。 If you know what is wrong with my test i would love to hear it. 如果您知道我的考试有什么毛病,我很想听听。 It helps me understand writing this kind of tests better. 它可以帮助我更好地理解编写此类测试。

My package.json for reference: 我的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"
  }
}

The reason the normalizeFinalResponse spy is never called is that addPost is calling the method directly and not using the spy that was created locally. 从来没有调用过normalizeFinalResponse间谍的原因是addPost直接调用了该方法,而不使用本地创建的间谍。

When you call sinon.spy() is creates a pass-thru function that tracks if this new method was executed. 当您调用sinon.spy()将创建一个直通函数来跟踪是否执行了此新方法。 This is by design, as you wouldn't want code constantly changing your functions out from under you. 这是设计使然,因为您不希望代码不断从下面改变您的功能。

Generally speaking, you would not care if normalizefinalResposne had executed at all. 一般来说,您根本不会在乎是否已执行normalizefinalResposne The only thing that you should be concerned with is that given input addPost(x) it returns y the internal details do not matter so long as for input x you get result y . 您唯一需要关注的是,给定输入addPost(x)会返回y ,内部细节并不重要,只要输入x得到结果y This also simplifies refactoring later as your unit test will only break if the functionality stops working as expected, not just because the code looks different while the functionality remains the same. 这也简化了以后的重构,因为只有在功能按预期停止运行时,单元测试才会中断,这不仅仅是因为代码看起来不同而功能保持不变。

There are some exceptions to this rule, mostly when using 3rd party code. 该规则有一些例外,主要是在使用第三方代码时。 One such example is in doARequest . 这样的示例之一在doARequest You may want to use a mock library to override the http module so that it returns crafted responses and does not make live network requests. 您可能想使用模拟库来覆盖http模块,以便它返回精心制作的响应,并且不会发出实时网络请求。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM