5

I am extending net.Socket. In doing so, I am overriding the connect method as follows.

class ENIP extends Socket {
    constructor() {
        super();

        this.state = {
            session: { id: null, establishing: false, established: false },
            error: { code: null, msg: null }
        };
    }

    connect(IP_ADDR) {
        const { registerSession } = encapsulation; //returns a buffer to send

        this.state.session.establishing = true;

        return new Promise(resolve => {
            super.connect(EIP_PORT, IP_ADDR, () => { // This is what i want to mock -> super.connect
                this.state.session.establishing = false;

                this.write(registerSession());
                resolve();
            });
        });
    }
}

I want to mock the underlying Socket class so that I can simulate super.connect. Having viewed Facebook's docs on the matter, I am unsure on how to proceed with developing tests for this class as all methods will extend super.someMethodToMock. Does anyone know an approach I should take? Please let me know if there are any clarifying details I can provide.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
canaan seaton
  • 6,688
  • 4
  • 17
  • 25
  • Don't use inheritance. – Aluan Haddad Feb 20 '18 at 22:17
  • 1
    I think inheritance makes the solution much cleaner. – canaan seaton Feb 21 '18 at 17:52
  • But more difficult to test. The cleaner the code, the easier to test, to paraphrase Uncle Bob, the author of clean code and perhaps the foremost advocate of TDD ;) – Aluan Haddad Feb 21 '18 at 17:54
  • Besides, delegation in JavaScript is so trivial, requiring merely a single line of code in this case, that you would do well to use dependency injection. One reason you might use inheritance would be if you're passing it to an API that does instance of tests. There are ways around that, but if you're doing the tests yourself then you're writing bad JavaScript – Aluan Haddad Feb 21 '18 at 17:57
  • Anyway, if you don't want to change your code, the simplest solution would be to export a higher-order class, at class factory if you will, in addition to your current API, that you might pass in the class being extended. – Aluan Haddad Feb 21 '18 at 18:03
  • Does this answer your question? [Jest mock method of base ES6 class (super method) when testing extended class](https://stackoverflow.com/questions/65391369/jest-mock-method-of-base-es6-class-super-method-when-testing-extended-class) – Liam Nov 24 '22 at 19:33

1 Answers1

4

You can use jest.spyOn(object, methodName) to create mock methods on Socket.prototype.

E.g.

index.js:

import { Socket } from 'net';

const EIP_PORT = 3000;
const encapsulation = {
  registerSession() {
    return 'session';
  },
};

export class ENIP extends Socket {
  constructor() {
    super();
    this.state = {
      session: { id: null, establishing: false, established: false },
      error: { code: null, msg: null },
    };
  }

  connect(IP_ADDR) {
    const { registerSession } = encapsulation;
    this.state.session.establishing = true;

    return new Promise((resolve) => {
      super.connect(EIP_PORT, IP_ADDR, () => {
        this.state.session.establishing = false;
        this.write(registerSession());
        resolve();
      });
    });
  }
}

index.test.js:

import { ENIP } from './';
import { Socket } from 'net';

describe('ENIP', () => {
  afterAll(() => {
    jest.restoreAllMocks();
  });
  describe('#connect', () => {
    it('should pass', async () => {
      const writeSpy = jest.spyOn(Socket.prototype, 'write').mockImplementation();
      const connectSpy = jest.spyOn(Socket.prototype, 'connect').mockImplementationOnce((port, addr, callback) => {
        callback();
      });
      const enip = new ENIP();
      await enip.connect('localhost');
      expect(writeSpy).toBeCalledWith('session');
      expect(connectSpy).toBeCalledWith(3000, 'localhost', expect.any(Function));
    });
  });
});

unit test result with coverage report:

 PASS  src/stackoverflow/48888509/index.test.jsx (10.43s)
  ENIP
    #connect
      ✓ should pass (7ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 index.jsx |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.357s

source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/48888509

Lin Du
  • 88,126
  • 95
  • 281
  • 483