【问题标题】:Sinon spy.called no workingSinon spy.call 没有工作
【发布时间】:2017-05-12 14:51:54
【问题描述】:

背景

我有一个从机器接收数据的小型服务器。每次收到消息时,我都会调用调度程序对象中的一个函数,该函数只是 console.logs 它接收到的所有内容。

问题

代码运行良好,我可以在控制台中看到console.logs,但Sinon spy.called 不起作用。不管我打多少次dispatcher.onMessage,它总是false

代码

server.js

const eventDispatcher = {
    onMessage: console.log,
};

const server = (dispatcher = eventDispatcher) => {


    //this gets called everytime the server receives a message
    const onData = data => {

        //Process data
        //....
        dispatcher.onMessage(data);
    };


    const getDispatcher = () => dispatcher;

    return Object.freeze({
        getDispatcher
    });
};

test.js

describe("message sender", () => {

    const myServer = serverFactory();


    it("should send information to server", () => {
        dummyMachine.socket.write("Hello World!\r\n");

        const dataSpy = sinon.spy(myServer.getDispatcher(), "onMessage");
        expect(dataSpy.called).to.be.true; //always fails!
    });

});

研究

在阅读了类似的帖子后,我认为这是由于某种间接层造成的,如下所示:

并且应该通过使用this来修复:

但是,看着我的代码,我真的无法得到我所缺少的东西。

问题

  • 我做错了什么?

MCVE

目录结构

 Project_Folder
 |____package.json
 |____server.js
 |____test
      |____ dummyMachine_spec.js

package.json

{
  "name": "sinon-question",
  "version": "1.0.0",
  "description": "MCVE about a dummy machine connecting to a server for StackOverflow",
  "main": "server.js",
  "scripts": {
    "test": "NODE_ENV=test mocha --reporter spec --slow 5000 --timeout 5000 test/*_spec.js || true"
  },
  "author": "Pedro Miguel P. S. Martins",
  "license": "ISC",
  "devDependencies": {
    "chai": "^3.5.0",
    "mocha": "^3.3.0",
    "sinon": "^2.2.0"
  },
  "dependencies": {
    "net": "^1.0.2"
  }
}

server.js

"use strict";

const net = require("net");

const eventDispatcher = {
    onMessage: console.log,
};

const server = (dispatcher = eventDispatcher) => {

    let serverSocket;

    const onData = data => {
        //Process data
        dispatcher.onMessage(`I am server and I got ${data}`);
    };

    const start = (connectOpts) => {
        return new Promise(fulfil => {
            serverSocket = net.createConnection(connectOpts, () => {
                serverSocket.on("data", onData);   
                fulfil();
            });
        });
    };

    const stop = () => serverSocket.destroy();

    const getDispatcher = () => dispatcher;

    return Object.freeze({
        start,
        stop,
        getDispatcher
    });
};

module.exports = server;

test/dummyMachine.js

"use strict";


const chai = require("chai"),
    expect = chai.expect;

const sinon = require("sinon");
const net = require("net");
const serverFactory = require("../server.js");

describe("Dummy Machine", () => {

    const dummyMachine = {
        IP: "localhost",
        port: 4002,
        server: undefined,
        socket: undefined
    };

    const server = serverFactory();

    before("Sets up dummyReader and server", done => {

        dummyMachine.server = net.createServer(undefined, socket => {
            dummyMachine.socket = socket;
        });

        dummyMachine.server.listen(
            dummyMachine.port,
            dummyMachine.IP,
            undefined,
            () => {
                server.start({
                    host: "localhost",
                    port: 4002
                })
                .then(done);
            }
        );
    });

    after("Kills dummyReader and server", () => {
        server.stop();
        dummyMachine.server.close();
    });

    it("should connect to server", done => {
        dummyMachine.server.getConnections((err, count) => {
            expect(err).to.be.null;
            expect(count).to.eql(1);
            done();
        });

    });

    it("should send information to server", () => {
        dummyMachine.socket.write("Hello World\r\n");

        const dataSpy = sinon.spy(server.getDispatcher(), "onMessage");
        expect(dataSpy.called).to.be.true; //WORK DAAMN YOU!
    });
});

MCVE 说明

  1. 下载文件并创建指定的目录结构。
  2. 进入项目文件夹,在终端输入npm install
  3. 输入npm test

第一个测试应该通过,这意味着实际上正在建立连接。

第二个测试会失败,即使你得到了控制台日志,证明onMessage被调用了。

【问题讨论】:

  • 那么onMessage 会在哪里被调用?在创建间谍和检查它是否被调用之间,我没有看到任何其他代码正在运行。
  • 服务器收到数据时,调用onMessage。 console.log 显示,位间谍不起作用...
  • 您正在创建间谍并立即检查它是否被调用。 Node.js 不是多线程的,因此这两个操作之间没有上下文切换(即服务器接收任何内容的时间)。
  • 这让我很困惑。你是在告诉我,如果我执行 1 秒的 setTimeout 然后尝试调用间谍,它会起作用吗?
  • 可能,但解决这个问题的正确方法是实现异步测试。但是,您在此处发布的代码无法提供足够的洞察力来确定您的应用程序中的所有内容是如何连接的。例如,dummyMachine.socket.write() 在做什么?考虑创建一个MCVE

标签: javascript node.js testing sinon


【解决方案1】:

主要问题是仅仅监视onMessage 是不够的,因为您的测试永远不会发现何时它被准确调用(因为流事件是异步传递的)。

您可以使用setTimeout() 进行黑客攻击,并检查它是否在向服务器发送消息后的某个时间被调用,但这并不理想。

相反,您可以将 onMessage 替换为将被调用的函数,然后通过该函数,您可以测试并查看它是否使用正确的参数等被调用。

诗浓提供了stubs,可用于此:

it("should send information to server", done => {
  const stub = sinon.stub(server.getDispatcher(), 'onMessage').callsFake(data => {
    stub.restore();
    expect(data).to.equal('I am server and I got Hello World\r\n');
    done();
  });
  dummyMachine.socket.write("Hello World\r\n");
});

代替原来的onMessage,它将调用您提供的“假函数”。在那里,存根被恢复(这意味着onMessage 恢复到原来的状态),您可以检查它是否被正确的参数调用。

因为测试是异步的,所以它使用done

有几点需要考虑:

  • 您无法轻松检测到是否由于编程错误,onMessage 根本没有被调用。发生这种情况时,几秒钟后,Mocha 会使您的测试超时,导致测试失败。
  • 如果发生这种情况,存根将不会恢复到其原始状态,并且任何尝试存根 onMessage 的后续测试都将失败,因为该方法已被存根(通常,您可以通过在 @ 中创建存根来解决此问题987654331@ 并在onAfterEach 中恢复它)
  • 此解决方案不会测试onMessage 的内部工作原理,因为它正在被替换。它只测试它是否被调用,并使用正确的参数(但是,单独测试 onMessage 更好,更容易,直接使用测试用例中的各种参数调用它)。

【讨论】:

  • 惊人的输入!我使用了你答案的变体。这正是我所需要的!
【解决方案2】:

我猜这个问题是由于在您想要监视的对象上使用Object.freeze 引起的。

大多数情况下,这些“窥探”技术通过用另一个实现“窥探”功能的函数(例如:跟踪函数调用)覆盖被窥探的函数来工作。

但是如果你冻结了对象,那么函数就不能被覆盖。

【讨论】:

  • 解除冻结并不能解决任何问题(已测试)。此外,如果这是问题所在,我实际上会在测试运行之前出现运行时错误,但事实并非如此!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-23
  • 2016-10-18
  • 1970-01-01
  • 2015-10-10
  • 2018-05-27
  • 2017-12-09
相关资源
最近更新 更多