33

I'd like to unit test the following ES6 class:

// service.js
const InternalService = require('internal-service');

class Service {
  constructor(args) {
    this.internalService = new InternalService(args);
  }

  getData(args) {   
    let events = this.internalService.getEvents(args);
    let data = getDataFromEvents(events);
    return data;
  }
}

function getDataFromEvents(events) {...}

module.exports = Service;

How do I mock constructor with Sinon.JS in order to mock getEvents of internalService to test getData?

I looked at Javascript: Mocking Constructor using Sinon but wasn't able to extract a solution.

// test.js
const chai = require('chai');
const sinon = require('sinon');
const should = chai.should();

let Service = require('service');

describe('Service', function() {
  it('getData', function() {
    // throws: TypeError: Attempted to wrap undefined property Service as function
    sinon.stub(Service, 'Service').returns(0);
  });
});
Community
  • 1
  • 1
krl
  • 5,087
  • 4
  • 36
  • 53
  • Take a look at this question: [Javascript: Mocking Constructor using Sinon](https://stackoverflow.com/questions/14569499/javascript-mocking-constructor-using-sinon). – sdgluck Sep 13 '15 at 13:48
  • @sdgluck I did, but I wasn't able to extract the solution for my case – krl Sep 13 '15 at 13:51
  • Possible duplicate of [Javascript: Mocking Constructor using Sinon](https://stackoverflow.com/questions/14569499/javascript-mocking-constructor-using-sinon) – Jon Schneider Jan 13 '18 at 00:41

2 Answers2

27

You can either create a namespace or create a stub instance using sinon.createStubInstance (this will not invoke the constructor).

Creating a namespace:

const namespace = {
  Service: require('./service')
};

describe('Service', function() {
  it('getData', function() {
    sinon.stub(namespace, 'Service').returns(0);
    console.log(new namespace.Service()); // Service {}
  });
});

Creating a stub instance:

let Service = require('./service');

describe('Service', function() {
  it('getData', function() {
    let stub = sinon.createStubInstance(Service);
    console.log(stub); // Service {}
  });
});
sdgluck
  • 24,894
  • 8
  • 75
  • 90
victorkt
  • 13,992
  • 9
  • 52
  • 51
  • thanks! and how do I add a mock of `this.internalService.getEvents` returning a string? – krl Sep 13 '15 at 16:18
  • In this answer `stub` will have each method from `internalService` as a `sinon.stub`. So you can do this: `stub.getEvents.returns("some string")` – sdgluck Sep 13 '15 at 17:23
  • 1
    @sdgluck I figured it out. This is what I needed `let stub = sinon.createStubInstance(Service); stub.internalService = { getDataFromEvents: function() { return 'test'; } }; expect(stub.getData()).to.equal(...);` – krl Sep 13 '15 at 17:26
  • 1
    @krl I don't think you are doing things the right way now. You are now just overriding the property 'internalService' after the instance is created. This is not stubbing InternalService with sinon as you probably would want to do. – Bas Slagter Apr 01 '16 at 08:58
  • The `Creating a namespace:` suggestion in answer provided above worked like a charm for me. I only had to make sure that in the code being tested as well that I imported via the name space import pattern suggested above: Changed `const { FuncName } = require('..')` to `const nameSpace = require('..')`, and in the rest of the code prefixed `FuncName` with `nameSpace.FuncName` wherever it gets invoked with the `new` keyword when creating objects: `new nameSpace.FuncName(...)` instead of `new FuncName(...)`. In my test JS I create spy like this: `sinon.spy(nameSpace, "FuncName"). – Jose Quijada Aug 11 '19 at 16:08
0

You could also use proxyquire:

const proxyquire = require('proxyquire');
const sinon = require('sinon');

describe('Service', function() {
    it('getData', function() {
        const internalServiceStub = {
            getEvents: sinon.stub() // mock however you wish
        };
        const InternalServiceStub = sinon.stub().callsFake(() => internalServiceStub);
        const ServiceStub = proxyquire('./service', {
            "./internal-service": InternalServiceStub
        });

        const serviceStub = new ServiceStub();
        // call serviceStub.getData(...) with mocked internalService
    });
});

Notice that this solution will not invoke the constructor.

Daniel
  • 198
  • 2
  • 14