使用Jasmine进行单元测试时避免副作用

时间:2017-12-18 16:42:27

标签: node.js unit-testing jasmine

我正在尝试使用测试库node-jasmine:

对下面的函数进行单元测试
joinGame(participant) {
    console.log('Joining game', participant);

    if (this.getParticipants().length >= MAX_NUMBER_OF_PARTICIPANTS) {
        throw new Error(`The game with id ${this.getId()} has reached the maximum amount of participants, ${MAX_NUMBER_OF_PARTICIPANTS}`);
    }

    this.addParticipant(participant);
    this.incrementPlayerCount();

    this.emit(actions.GAME_JOINED, participant);

    // Is the game ready to start?
    if (this.playerCount >= REQUIRED_NUMBER_OF_PARTICIPANTS) {
        // Start game loop by initializing the first round
        this.createRound();
    }
}

然而,当单元测试函数时,一些代码路径引导我调用位于函数末尾的'this.createRound()'。 createRound()基本上初始化游戏循环,启动计时器,以及与我单元测试功能完全无关的其他副作用。看看下面的测试:

it('should throw an error if a user tries to join a game with the maximum amount of participants has been reached', () => {
    game = new Game();

    // To test whenever there are two participants in the game
    game.joinGame(hostParticipant);
    game.joinGame(clientParticipant);

    function testJoin() {
        game.joinGame(joiningParticipant);
    }

    expect(testJoin).toThrow();
});

现在,当我运行测试时,测试将反对我的意志调用'createRound()'。 'createRound()'实例化Round实例并启动倒数计时器,这使得我的命令行中的'npm test'调用永远不会完成。由于测试认为它是测试的一部分。

以下是我想到并实施的一些方法。虽然,我觉得他们中的任何一个都不“干净”,这就是为什么我在寻找你的意见。

方法1:在测试中存储'createRound()'以替换其功能。这工作正常,但它是避免调用副作用的正确方法吗?

方法2:尝试在beforeEach / afterEach之前设置/拆除Game实例。我尝试过这种方法没有成功。但是,通过在'afterEach()'上将游戏实例设置为null,实例化的圆形实例将继续与其计时器一起运行。

方法3:在调用'joinGame()'时使用依赖注入并提供Round实例。这没有多大意义,因为在调用'joinGame()'时,客户端不应该提供新的圆形实例。此外,并非每次调用'joinGame()'都会调用'createRound()';只有当玩家数量超过所需的玩家数量时才会显示。

1 个答案:

答案 0 :(得分:2)

Stubbing createRound当然有道理。您正在编写测试来断言拒绝用户加入完整游戏的行为,而不是计时器是否按预期工作。如果你在被测对象上存根方法,这会有点毛茸茸,但后来我认为管理计时器的逻辑可能属于它自己的独立对象。

当然,您也可以考虑:

方法4:按照茉莉花documentation中的描述模拟时钟。假设定时器依赖于setTimeout / setInterval,您可以在调用函数之前安装假时钟,并手动勾选时钟以获取可以进行断言的状态。