如何使用 ProxyRequire 模拟 NodeJs 测试

How to use ProxyRequire to mock NodeJs test

我正在为以下文件编写单元测试用例

//filename: a.js

var amqp = require("amqplib");

class RMQ {
  constructor(connectionURI) {
    this.URI = connectionURI;
  }
  async getInstance() {
    var connection = await amqp.connect(this.URI);
    this.connection = connection;
    return connection;
  }
}

const RMQ_INSTANCE = new RMQ(process.env.RMQ_URL);

module.exports = {
  RMQ_INSTANCE,
};

我正在使用下面文件

中的实例RMQ_INSTANCE
// filename: b.js

const { RMQ_INSTANCE } = require("./a");

module.exports.publishEmail = async function(message) {
  var connection = await RMQ_INSTANCE.getInstance();
  var channel = await connection.createChannel();
  var exchange = "some_exchange";
  var key = "some_key";
  var msg = JSON.stringify(message);
  await channel.assertExchange(exchange, "topic", { durable: false });
  await channel.publish(exchange, key, Buffer.from(msg));
  setTimeout(function () {
    connection.close();
  }, 500);
}

我正在使用 proxyrequire 模拟 b.js

中的 RMQ_INSTANCE
// filename: b.test.js

var proxyrequire = require("proxyquire").noCallThru();
var sinon = require("sinon");
const { assert } = require("sinon");

class fakeRMQClass {
  constructor(connectionURI) {
    this.URI = connectionURI;
  }
  async getInstance() {
    var connection = getFakeRMQStub()
    return connection;
  }
}

var producerTest = function () {
  it("producer connection - success test", async function () {
    var fakeRMQInstance = new fakeRMQClass("fake_url");
    var rmqUtils = proxyrequire("../path/to/b.js", {
      "./a": fakeRMQInstance
    });
    await rmqUtils.publishEmail("fake_msg");
  });
  afterEach(function () {
    sinon.verifyAndRestore();
  });
};
describe("test_producer", producerTest);

但我发现模拟无法正常工作。谁能帮我正确地模拟这个?

因为 RMQ_INSTANCE 是一个对象,你可以使用 sinon.stub(obj, 'method') 存根它的方法,你不需要使用 proxyquire 包。

既然要测试b模块,那么b模块只关心它所依赖的RMQ_INSTANCE接口,具体实现无所谓。

b.js:

const { RMQ_INSTANCE } = require('./a');

module.exports.publishEmail = async function (message) {
  var connection = await RMQ_INSTANCE.getInstance();
  var channel = await connection.createChannel();
  var exchange = 'some_exchange';
  var key = 'some_key';
  var msg = JSON.stringify(message);
  await channel.assertExchange(exchange, 'topic', { durable: false });
  await channel.publish(exchange, key, Buffer.from(msg));
  setTimeout(function () {
    connection.close();
  }, 500);
};

a.js:

class RMQ {
  constructor(connectionURI) {
    this.URI = connectionURI;
  }
  async getInstance() {}
}

const RMQ_INSTANCE = new RMQ(process.env.RMQ_URL);

module.exports = { RMQ_INSTANCE };

b.test.js:

const sinon = require('sinon');
const { RMQ_INSTANCE } = require('./a');
const { publishEmail } = require('./b');

describe('test_producer', () => {
  let clock;
  before(() => {
    clock = sinon.useFakeTimers();
  });
  after(() => {
    clock.restore();
  });
  it('producer connection - success test', async () => {
    const channelStub = {
      assertExchange: sinon.stub().returnsThis(),
      publish: sinon.stub(),
    };
    const connectionStub = { createChannel: sinon.stub().resolves(channelStub), close: sinon.stub() };
    sinon.stub(RMQ_INSTANCE, 'getInstance').resolves(connectionStub);
    await publishEmail('fake message');
    sinon.assert.calledOnce(RMQ_INSTANCE.getInstance);
    sinon.assert.calledOnce(connectionStub.createChannel);
    sinon.assert.calledWithExactly(channelStub.assertExchange, 'some_exchange', 'topic', { durable: false });
    sinon.assert.calledWithExactly(
      channelStub.publish,
      'some_exchange',
      'some_key',
      Buffer.from(JSON.stringify('fake message')),
    );
    clock.tick(500);
    sinon.assert.calledOnce(connectionStub.close);
  });
});

测试结果:

  test_producer
    ✓ producer connection - success test


  1 passing (10ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |      75 |     100 |                   
 a.js     |     100 |      100 |      50 |     100 |                   
 b.js     |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------