Sinon spy.called no working

Background

I have a small server that receives data from a machine. Every time I get a message, I call a function on the dispatcher object that just console.log

gets everything.

Problem

The code works well as I can see console.log

in the console, but Sinon spy.called

doesn't work. It is always false

no matter how many times I call dispatcher.onMessage

.

code

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!
    });

});

      

Study

After reading posts like this, I believe this is due to some layer of indirection as stated in:

And you need to fix it with this

:

However, looking at my code, I really can't get what I'm missing.

Question

  • What am I doing wrong?

MCVE

Directory structure

 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!
    });
});

      

Instructions for MCVE

  • Download the files and create the specified directory structure.
  • Enter your project folder and type npm install

    in terminal
  • A type npm test

The first test must pass, which means the connection is actually being made.

The second test will fail even if you get a console log confirming that it was called onMessage

.

+3


source to share


2 answers


The main problem is that it is not enough just to track onMessage

, because your test will never know exactly when it received the call (because the thread events are delivered asynchronously).

You can use the hack with help setTimeout()

, and check and see if it is triggered some time after the message is sent to the server, but this is not ideal.

Instead, you can replace onMessage

with a function to be called instead, and from that function you can check and see if the correct arguments were called, etc.

Sinon provides stubs that can be used for this:

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");
});

      



Instead of the original, onMessage

it will call the "fake function" you provide. There the stub is restored (meaning it is onMessage

restored to the original), and you can check and see if it has called the correct argument.

Since the test is asynchronous, it uses done

.

There are several things to consider:

  • You cannot easily find that, due to a programming error, onMessage

    does not trigger the call at all. When this happens, Mocha will delay your test after a few seconds, causing it to fail.
  • If this happens, the stub will not be restored to its original, and any subsequent tests that try to stub onMessage

    will fail because the method is already shaded (usually you work around this by creating a stub in onBeforeEach

    and restoring it in onAfterEach

    )
  • This solution will not check the inner workings onMessage

    as it is being replaced. It only checks if it will be called and with the correct argument (however, it is better and easier to test onMessage

    separately, by calling it directly with different arguments from your test cases).
+1


source


I would guess the problem is caused by using the Object.freeze

object you would like to keep track of.

In most cases, these "spy" methods work by overwriting the spy function with another function that implements the "spy" functionality (for example: tracking function calls).



But if you freeze the object, the function cannot be overwritten.

0


source







All Articles