【问题标题】:Can't properly mock a promise and callback无法正确模拟承诺和回调
【发布时间】:2020-11-13 09:29:12
【问题描述】:

我正在尝试在 js 文件 'eslModule.js' 中测试以下方法,但没有成功


async function exec(cmd, args, ip, port) {
    const connPromise = new Promise(function(resolve, reject) {

        try {
            sock = new esl.Connection(ip, port, 'pwd', function() {
                sock.bgapi(command, function(result) {
                    if (sock && sock.socket.writable && sock.socket.readable) {
                        sock.disconnect();
                    }
                    return resolve({
                        result: result.body
                    });
                });
            });
        } catch (error) {
            return reject(error);
        }

        sock.on('error', function(error) {

            if (sock === null) {
                return false;
            }
            if (error.code === 'ERR1' || error.code === 'ERR2') {
                error.message = `Connection failed: [${error.code}]`;
            }
            if (sock && sock.socket.writable && sock.socket.readable) {
                sock.disconnect();
            }

            return reject(error);
        });

        try {
            await connPromise;
        } catch (error) {
            sock = null;
            throw error;
        }
    });
}

这就是测试的样子

const esl = require('modesl');
const eslModule = rewire(`<PATH TO MODULE>`);
(...)

it('It should return a valid response', async function () {
        
    const cmd = "c";
    const args = "t";
    const ip = "127.0.0.1";
    const port = '<port>'

    const expResp = {
        result: '+OK---- 00000000-0000-0000-0000-000000000000'
     }

    stub1 = sinon.stub(esl, 'Connection').yields(ip, port, 'pwd',expResp);

    const result = await eslModule.exec(cmd, args, ip, port);
    expect(result).resolves.to.deep.equal(expResp);
    sinon.assert.calledOnce(stub1);
});

但我没有让它工作,因为测试进入了 Promise,然后在 sock.bgapi 回复无法访问未定义的 bgapi...

如果我尝试模拟错误(在 sock.on('error' ...) 中,我会得到类似的回复,即 .on 不是方法。

【问题讨论】:

    标签: node.js unit-testing chai sinon


    【解决方案1】:

    你需要伪造 bgapion 方法。

    这只是一个例子:我根据你的代码创建文件 eslModule.js。

    // File eslModule.js
    const esl = require('modesl');
    
    async function exec(cmd, args, ip, port) {
      return new Promise(function(resolve, reject) {
        try {
          sock = new esl.Connection(ip, port, 'pwd', function() {
            sock.bgapi(cmd, function(result) {
              if (sock && sock.socket.writable && sock.socket.readable) {
                sock.disconnect();
              }
              return resolve({
                result: result.body
              });
            });
          });
        } catch (error) {
          return reject(error);
        }
    
        sock.on('error', function(error) {
          if (sock === null) {
            return false;
          }
          if (error.code === 'ERR1' || error.code === 'ERR2') {
            error.message = `Connection failed: [${error.code}]`;
          }
          if (sock && sock.socket.writable && sock.socket.readable) {
            sock.disconnect();
          }
          return reject(error);
        });
      });
    }
    
    module.exports.exec = exec;
    

    然后是spec文件:eslModule.spec.js

    // File: eslModule.spec.js
    const esl = require('modesl');
    const sinon = require('sinon');
    const chai = require('chai');
    const chaiAsPromised = require('chai-as-promised');
    const eslModule = require('./eslModule');
    
    chai.use(chaiAsPromised);
    const { expect } = chai;
    
    it('It should return a valid response', function () {
      const cmd = 'c';
      const args = 't';
      const ip = '127.0.0.1';
      const port = '<port>';
      // Define output.
      const expResp = {
        result: '+OK---- 00000000-0000-0000-0000-000000000000',
      };
    
      // Prepare variable to store callback function.
      let connectionCallback;
      let bgApiCallback;
    
      // Create fake sock.
      const sockFake = {
        bgapi: sinon.fake((arg1, arg2) => {
          bgApiCallback = arg2;
        }),
        socket: {
          writable: true,
          readble: true,
        },
        disconnect: sinon.fake(),
        on: sinon.fake(),
      };
    
      // Create stub on esl.Connection.
      const stubEslConnection = sinon.stub(esl, 'Connection');
      stubEslConnection.callsFake((arg1, arg2, arg3, arg4) => {
        connectionCallback = arg4;
        return sockFake;
      });
    
      // Call exec function.
      const result = eslModule.exec(cmd, args, ip, port);
      // Make sure all callback function get called.
      connectionCallback();
      bgApiCallback({ body: expResp.result });
      // Result is Promise object, use with eventually.
      expect(result).eventually.to.deep.equal(expResp);
      // Makesure stub get called.
      expect(stubEslConnection.calledOnce).to.equal(true);
    
      // Do not forget to restore the stub.
      stubEslConnection.restore();
    });
    

    当我在终端上使用 mocha 运行它时

    $ npx mocha eslModule.spec.js 
    
    
      ✓ It should return a valid response
    
      1 passing (4ms)
    

    注意事项:

    • 我从不使用模型模块,我纯粹根据您的代码示例在上面创建测试,
    • 我选择使用 chai-as-promised,而不是让它异步运行。
    • spec 文件中最棘手的事情是:创建假 sock 对象并处理回调函数。

    【讨论】:

      猜你喜欢
      • 2021-03-18
      • 2020-11-29
      • 2014-10-18
      • 2016-06-15
      • 1970-01-01
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多