10

I am writing a Jest test in which I call a function and expect an object in return as follows:

const repository = container => {

  const makeBooking = (user, booking) => {
    'make booking function called'
  }

  const generateTicket = (paid, booking) => {
    console.log('generate ticket function called')
  }

  const getOrderById = orderId => {
    console.log('get order by ID called')
  }

  const disconnect = () => {
    console.log('disconnect method called')
  }

  return {
    makeBooking,
    getOrderById,
    generateTicket,
    disconnect
  }
}

All of these functions are now samples for the sake of example. I now, export the function and then use it in a test as follows:

container = {}

describe('Repository', () => {
  it('should connect with a container', () => {
    let hello = repository(container)

    expect(hello).toMatchObject({
      makeBooking: jest.fn('makeBooking'),
      getOrderById: jest.fn('getOrderById'),
      generateTicket: jest.fn('generateTicket'),
      disconnect: jest.fn('disconnect')
    })
  })
})


The Error I am getting is :

 Expected value to match object:
      {"disconnect": [Function mockConstructor], "generateTicket": [Function mockConstructor], "getOrderById": [Function mockConstructor], "makeBooking": [Function mockConstructor]}
    Received:
      {"disconnect": [Function disconnect], "generateTicket": [Function generateTicket], "getOrderById": [Function getOrderById], "makeBooking": [Function makeBooking]}

I need to mock the object being returned from the repository object. However, this object consists named functions as shown in the code. is there anyway to mock objects with functions inside them?

Ashif Shereef
  • 454
  • 1
  • 8
  • 24
  • 1
    It's not clear to me what you're trying to mock - if you're testing `repository.connect` you shouldn't be mocking the things inside it. If you just care about the shape you could do e.g. `expect(hello).toMatchObject({ makeBooking: expect.anything(), ... });`, but at some point you should also be testing those functions. – jonrsharpe Apr 17 '20 at 09:59
  • There was a slight error in the code I pasted above. I am not mocking connect, I am mocking the the return object from the repository object. – Ashif Shereef Apr 17 '20 at 10:15
  • Expect.anything() is working, but is there anyway I can ascertain that they are functions indeed? – Ashif Shereef Apr 17 '20 at 10:19
  • 1
    You don't show any of that mocking setup, though; it's unclear why you thought this test might pass. I think you can do `expect.any(Function)`, but this doesn't seem like a robust testing approach. – jonrsharpe Apr 17 '20 at 10:19
  • The scope of this unit, atomic test is to only test whether the repository, which is actually an adapter, would provide the necessary repository object once invoked. The implementation of these repository functions are to be tested somewhere else; not here. I hope you understand. Thank you for your help. – Ashif Shereef Apr 17 '20 at 12:35

2 Answers2

11

This is what I was looking for:

expect.objectContaining({ 
   theProperty: expect.any(Function) 
})
Ben
  • 54,723
  • 49
  • 178
  • 224
10

You can use jest.spyOn to make a stub for the methods of repository.

You mock or stub the methods of the repository, you need to use them. This is why the service.js come from, of course, you can use the repository anywhere. So, actually the method to be tested is service.makeBooking. Then, you can make assertion for the makeBooking method of the repository, for example, to check the mocked/stubbed makeBooking method of the repository to have been called or not.

And here we use dependency injection pattern to inject the mocked hello object to service.makeBooking(hello) method.

E.g.

repository.js:

const repository = (container) => {
  const makeBooking = (user, booking) => {
    'make booking function called';
  };

  const generateTicket = (paid, booking) => {
    console.log('generate ticket function called');
  };

  const getOrderById = (orderId) => {
    console.log('get order by ID called');
  };

  const disconnect = () => {
    console.log('disconnect method called');
  };

  return {
    makeBooking,
    getOrderById,
    generateTicket,
    disconnect,
  };
};

module.exports = repository;

repository.test.js:

const repository = require('./repository');

const container = {};

describe('Repository', () => {
  it('should connect with a container', () => {
    let hello = repository(container);

    expect(hello).toMatchObject({
      makeBooking: expect.any(Function),
      getOrderById: expect.any(Function),
      generateTicket: expect.any(Function),
      disconnect: expect.any(Function),
    });
  });

  it('should generate ticket', () => {
    let hello = repository(container);
    const logSpy = jest.spyOn(console, 'log');
    hello.generateTicket();
    expect(logSpy).toBeCalledWith('generate ticket function called');
  });

  // rest test cases same as above
});

unit test results with coverage report:

 PASS  stackoverflow/61268658/repository.test.js (11.281s)
  Repository
    ✓ should connect with a container (5ms)
    ✓ should generate ticket (19ms)

  console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
    generate ticket function called

---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |      80 |      100 |      40 |      80 |                   
 repository.js |      80 |      100 |      40 |      80 | 11,15             
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        13.132s

service.js:

const service = {
  makeBooking(hello) {
    return hello.makeBooking();
  },
};

module.exports = service;

service.test.js:

const service = require('./service');
const repository = require('./repository');
const container = {};

describe('service', () => {
  it('should init', () => {
    let hello = repository(container);
    jest.spyOn(hello, 'makeBooking').mockReturnValueOnce('fake data');
    const actual = service.makeBooking(hello);
    expect(actual).toEqual('fake data');
    expect(hello.makeBooking).toBeCalledTimes(1);
  });
});

unit test results with coverage report:

 PASS  stackoverflow/61268658/service.test.js (10.94s)
  service
    ✓ should init (4ms)

---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |   76.92 |      100 |   33.33 |   76.92 |                   
 repository.js |      70 |      100 |      20 |      70 | 7,11,15           
 service.js    |     100 |      100 |     100 |     100 |                   
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.278s
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • I don't understand the service.js file. Care to elucidate? I understand the intuition though ! – Ashif Shereef Apr 17 '20 at 12:43
  • Which thing do you think this is testing? The OP seems to be trying to test `repository`, but you've invented a `service` and appear to be testing *that* instead. In which case using the real repository is unnecessary, you can just pass e.g. `{ makeBooking: jest.fn() }` as the `hello` parameter. – jonrsharpe Apr 17 '20 at 13:14
  • @jonrsharpe Ok. What if using `const repository = require('./repository')` in `service.js` module rather than dependency injection pattern? The OP says he wants to mock/stub the methods of `repository`. – Lin Du Apr 17 '20 at 13:37
  • Well, what if? It's hard to say, because *you've invented the service*, we have no idea how the OP is actually using the repository. Their test is `describe('Repository'`, they're testing the repository itself. – jonrsharpe Apr 17 '20 at 13:40
  • 2
    I needed the expect.any(Function) so it was helpful to me. – ponder275 Oct 13 '20 at 21:28