用 mocha 和 sinon 测试插座

Test sockets with mocha and sinon

背景

我制作了一个通过套接字发出消息的小函数,我正在尝试使用 mocha 对其进行测试。

const myFun = socket => {

    socket.emit("first", "hello World!");

    setTimeout(() => {
        socket.emit("second", "hello Moon!");
    }, 1000);

    setTimeout(() => {
        socket.emit("third", "hello Mars? Venus? I dunno...");
    }, 2000);
};

因为我将套接字依赖项注入到使用它的函数中,所以只需将一个假套接字传递给它并测试它是否被调用以及使用哪些参数(例如,使用 sinon.js ).

问题

这里的问题是我不知道我的测试什么时候结束。因为 myFun 没有 return 承诺,也没有任何我不知道如何告诉 mocha 我已经发送了我想要的所有消息并且测试应该结束。

测试

目前,我正在使用以下代码:

const chai = require("chai");
const expect = chai.expect;
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);

describe("myFun", () => {

    const fakeSocket = {
            emit: sinon.spy()
        };

    it("receive first message", done => {

        myFun(fakeSocket);

        setTimeout(() => {
            try{
                expect(fakeSocket.emit).to.have.been.calledThrice;
                done();
            }catch(err){
                done(err);
            }
        }, 3000);
        //we wait 3 seconds because it should be more than enough for all messages to be delivered
    });

});

我正在使用 setTimeouttry catch 来测试代码,老实说这很糟糕。

问题

How can I improve my tests so they don't take 3 seconds and just end when I receive the last message?

哪条消息是最后一条消息?现在您发出三个消息,但是如果 myFun 更改为发出四个消息怎么办?还是四百?当涉及异步时,Mocha 无法知道 myFun 已完成,除非您告诉它。

也许您应该 socket.emit('done') 发送完所有消息后,然后您可以等待特定的发送将您的测试标记为完成。

你需要看的是Sinon fake timers

Sinon 中的假计时器允许您控制内置计时函数的输出,例如 Date 构造函数和 setTimeout/setInterval.

mocha 中的基本用法可能类似于:

let clock;
beforeEach(() => {
    clock = sinon.useFakeTimers();
});

it ('should do a thing', () => {
    //assertion
});

it ('should a thing 1000 ms later'), () => {
    clock.tick(1000);
    //assertion
});

afterEach(() => {
    clock.restore();
});

您可以使用fake timers虚拟地提前时钟:

describe("myFun", () => {
  const fakeSocket = { emit: sinon.spy() }

  beforeEach(() => {
    this.clock = sinon.useFakeTimers();
  });

  afterEach(() => {
    fakeSocket.emit.reset();
    this.clock.restore();
  });

  it('emit first message', () => {
    myFun(fakeSocket);
    expect(fakeSocket.emit).to.have.been.calledOnce;
  });

  it('emit second message after 1000ms', () => {
    myFun(fakeSocket);
    this.clock.tick(1000);
    expect(fakeSocket.emit).to.have.been.calledTwice;
  });

  it('emit third message after 2000ms', () => {
    myFun(fakeSocket);
    this.clock.tick(2000);
    expect(fakeSocket.emit).to.have.been.calledThrice;
  });
});

或者根本没有任何计时器(使 setTimeout 同步调用它获得的回调):

describe("myFun", () => {
  const fakeSocket = { emit: sinon.spy() }
  const stub       = sinon.stub(global, 'setTimeout').callsArg(0);

  after(() => {
    stub.restore();
  });

  it('should emit three messages', () => {
    myFun(fakeSocket);
    expect(fakeSocket.emit).to.have.been.calledThrice;
  });
});