Brandon Benefield
Brandon Benefield

Reputation: 1672

How to access a mock method returned from a mocked library

I'm mocking the @elastic/elasticsearch library and I want to test that the search method is called with the right arguments but I'm having issues accessing search from my tests.

In my ES mock I just export an object that includes a Client prop that returns another object that has the search prop. This is the way search is accessed from the library

const { Client } = require('@elastic/elasticsearch')
const client = new Client(...)
client.search(...)

__mocks__/@elastic/elasticsearch

module.exports = {
    Client: jest.fn().mockImplementation(() => {
        return {
            search: (obj, cb) => {
                return cb(
                    '',
                    {
                        statusCode: 200,
                        body: {
                            hits: {
                                hits: [
                                    {
                                        _source: esIndexes[obj.index]
                                    }
                                ]
                            }
                        }
                    }
                )
            }
        }
    })
}

__tests__/getAddresses.test.js

const { getAddresses } = require('../src/multiAddressLookup/utils/getAddresses')
const { Client } = require('@elastic/elasticsearch')

beforeEach(() => {
    process.env.ES_CLUSTER_INDEX = 'foo'
    process.env.ui = '*'
})

describe('multiAddressLookup', () => {
    test('Should return the correct premises data with only the relevant "forecasted_outages"', async () => {
        const event = {
            foo: 'bar'
        }
        const esQueryResponse = {
            "body": "\"foo\":\"bar\"",
            "headers": {"Access-Control-Allow-Origin": '*'},
            "statusCode": 200
        }

        await expect(getAddresses(event)).resolves.toEqual(esQueryResponse)
        expect(Client().search).toHaveBeenCalled()  // This fails with 0 calls registered
    })
})

Upvotes: 2

Views: 2135

Answers (1)

Brandon Benefield
Brandon Benefield

Reputation: 1672

I'm not sure of any exact documentation for this scenario but I got the idea while looking through the Jest: The 4 ways to create an ES6 Mock Class - Automatic mock portion of the Jest documentation.

First, the search method in the ES mock, __mocks__/@elastic/elasticsearch, needs to be converted into a jest mock function, jest.fn(). Doing this gives us access to properties and values that jest mocks provide.

__mocks__/@elastic/elasticsearch.js converted

module.exports = {
    Client: jest.fn().mockImplementation(() => {
        return {
            search: jest.fn((obj, cb) => {
                return cb(
                    '',
                    {
                        statusCode: 200,
                        body: {
                            hits: {
                                hits: [
                                    {
                                        _source: esIndexes[obj.index]
                                    }
                                ]
                            }
                        }
                    }
                )
            })
        }
    })
}

Second, in our tests we need to follow the path from the Client mock class until we find out methods. The syntax is MockClass.mock.results[0].value.mockFunction.

Example Test

const { Client } = require('@elastic/elasticsearch')  // This is located in the "__mocks__" folder in the root of your project

const { getAddresses } = require('../../src/getAddresses')  // This is the file we wrote and what we are unit testing

describe('getAddresses', () => {
    it('Should call the ES Search method', async () => {
        const event = { ... }
        const expected = { ... }
        await expect(getAddresses(event)).resolves.toEqual(expected)  // pass
        expect(Client.mock.results[0].value.search).toHaveBeenCalled()  // pass
    })
})

Upvotes: 2

Related Questions