Search code examples
node.jsunit-testingsinon

How to test listener function inside childporcess.on exit event


I am not able to increase the test coverage/test function which is invoked by a child process on exit. And I want to mock fork('src/services/ssync-process.js');

log.info('Child process sProcessJob finished execution'); sProcess.kill();

are never covered

Test.js

const sinon = require('sinon');
const rewire = require('rewire');
const { EventEmitter } = require('events');
const job = rewire('../../src/services/job');

it('test exit', () => {
    const mockChildProcess = new EventEmitter();

    mockChildProcess.emit('exit');
    mockChildProcess.kill = sinon.stub();
    mockChildProcess.killed = true;
    job.__set__({
      sProcess: mockChildProcess,
    });

    job.sSyncJob();
  });

Job.js

const { fork } = require('child_process');

let sProcess;

const sSyncJob = () => {
  if (!sProcess || sProcess.killed) {
    sProcess = fork('src/services/ssync-process.js');
    sProcess.send({ hello: 'world' });
    sProcess.on('message', (msg) => {
      log.info('Message from child', msg);
    });
    sProcess.on('exit', () => {
      log.info('Child process sProcessJob finished execution');
      sProcess.kill();
    });
  }
};

Solution

  • Here is the unit test solution using Link Seams, proxyquire package and rewire package.

    E.g.

    job.js:

    let { fork } = require('child_process');
    
    const log = {
      info: console.info,
    };
    
    let sProcess;
    
    const sSyncJob = () => {
      if (!sProcess || sProcess.killed) {
        sProcess = fork('src/services/ssync-process.js');
        sProcess.send({ hello: 'world' });
        sProcess.on('message', (msg) => {
          log.info('Message from child', msg);
        });
        sProcess.on('exit', () => {
          log.info('Child process sProcessJob finished execution');
          sProcess.kill();
        });
      }
    };
    
    exports.sSyncJob = sSyncJob;
    

    job.test.js:

    const sinon = require('sinon');
    const proxyquire = require('proxyquire');
    const rewire = require('rewire');
    
    describe('61561718', () => {
      afterEach(() => {
        sinon.restore();
      });
      it('should receive message', () => {
        const logSpy = sinon.spy(console, 'info');
        const child_process_fork_stub = {
          send: sinon.stub().returnsThis(),
          on: sinon
            .stub()
            .returnsThis()
            .yields('fake message'),
          kill: sinon.stub(),
        };
        const child_process_stub = {
          fork: sinon.stub().returns(child_process_fork_stub),
        };
        const job = proxyquire('./job', {
          child_process: child_process_stub,
        });
        job.sSyncJob();
        sinon.assert.calledWithExactly(child_process_stub.fork, 'src/services/ssync-process.js');
        sinon.assert.calledWithExactly(child_process_fork_stub.send, { hello: 'world' });
        sinon.assert.calledWithExactly(child_process_fork_stub.on, 'message', sinon.match.func);
        sinon.assert.calledWithExactly(child_process_fork_stub.on, 'exit', sinon.match.func);
        sinon.assert.calledWithExactly(logSpy, 'Message from child', 'fake message');
        sinon.assert.calledWithExactly(logSpy, 'Child process sProcessJob finished execution');
        sinon.assert.calledOnce(child_process_fork_stub.kill);
      });
    
      it('should not fork child process if sProcess exists', () => {
        const job = rewire('./job');
        const child_process_stub = {
          fork: sinon.stub(),
        };
        job.__set__({
          sProcess: {},
          child_process: child_process_stub,
        });
        job.sSyncJob();
        sinon.assert.notCalled(child_process_stub.fork);
      });
    });
    

    The first test case uses proxyquire to test, the second test case uses rewire to test.

    Unit test results with 100% coverage:

      61561718
    Message from child fake message
    Child process sProcessJob finished execution
        ✓ should receive message
        ✓ should not fork child process if sProcess exists
    
    
      2 passing (52ms)
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |     100 |      100 |     100 |     100 |                   
     job.js   |     100 |      100 |     100 |     100 |                   
    ----------|---------|----------|---------|---------|-------------------