Search code examples
javascriptunit-testingmocha.jschaisinon

How to mock a socket connection properly to simulate various on.('data' events


I'm testing a method that returns a Promise based on the data filtered from a socket:

async readData(sConn) {
    if(!sConn) {
        throw new Error('READ_SOCK_ERROR');
    }

    let rawData;

    return new Promise((resolve, reject) => {
        sConn.on('data', async (data) => {
            try {
                rawData = data.toString();
                return resolve(rawData);
            } catch (error) {
                return reject(new Error(`Error reading response. Details: ${error.stack}`));
            }
        }
    }
}

How can I mock the socket connection properly so I can simulate various data sent over in the data event? This is what I'm using but the test hangs and eventualy get's to a timeout....

it('It should not throw error and return simulated data string', async function () {
        let result;
        let error = false;
        let mockedData = 'This is\na simultated response\nfrom the socket';
        
        const sConn = {
            on: () => Promise.resolve(mockedData),
        };

        try {
            result = await rewiredFileContainingMethodToTest.readData(sConn);
            console.log(result);
        } catch (err) {
            error = err;
            console.log('Error caught: '+err);
        }
        expect(error).to.be.false;
        sinon.assert.callCount(sConn.on, 1);
        expect(result).contains(mockedData);
    });

Solution

  • You can use stub.callsFake(fakeFunction), it makes the stub call the provided fakeFunction when invoked.

    E.g.

    file.js:

    const file = {
      async readData(sConn) {
        if (!sConn) {
          throw new Error('READ_SOCK_ERROR');
        }
    
        let rawData;
    
        return new Promise((resolve, reject) => {
          sConn.on('data', async (data) => {
            try {
              rawData = data.toString();
              return resolve(rawData);
            } catch (error) {
              return reject(new Error(`Error reading response. Details: ${error.stack}`));
            }
          });
        });
      },
    };
    
    export { file };
    

    file.test.js:

    import { expect } from 'chai';
    import sinon from 'sinon';
    import { file } from './file';
    
    describe('65594632', () => {
      it('It should not throw error and return simulated data string', async function () {
        let mockedData = 'This is\na simultated response\nfrom the socket';
    
        const sConn = {
          on: sinon.stub().callsFake(async (event, callback) => {
            await callback(mockedData);
          }),
        };
    
        const result = await file.readData(sConn);
        sinon.assert.callCount(sConn.on, 1);
        expect(result).to.be.equal(mockedData);
      });
    });
    

    unit test result:

      65594632
        ✓ It should not throw error and return simulated data string
    
    
      1 passing (7ms)
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |   81.82 |       50 |     100 |      80 |                   
     file.ts  |   81.82 |       50 |     100 |      80 | 4,15              
    ----------|---------|----------|---------|---------|-------------------