Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Mocking local variable in the scope of original module #146

Open
gnom1gnom opened this issue Jun 27, 2023 · 1 comment
Open

Question: Mocking local variable in the scope of original module #146

gnom1gnom opened this issue Jun 27, 2023 · 1 comment

Comments

@gnom1gnom
Copy link

Inside my CommonJS Express app, I want to use rewiremock to replace the default Elasticsearch client with Elasticsearch mock. This package allows to intercept a desired request pattern and mock the response, like so:

const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock();
mock.add({
    method: 'GET',
    path: '/_cat/health'
}, () => {
    return `mocked health`
});

In a simplified example, my service has two methods -

/** @type {import('@elastic/elasticsearch').Client} */
let client = null;

module.exports.init = () => {
    console.log('Actual init...');
    return new Promise((resolve, reject) => {
        getAuthSecret().then(secret => {
            client = new Client({
                cloud: { id: process.env.ELASTIC_HOST },
                auth: { apiKey: secret }
            });
            resolve('Client initialize successfully');
        }).catch(error => { reject(error) })
    });
}
  • health, which is calling the health function from the client API
module.exports.health = async () => {
    console.log('Actual health...');
    return await client?.cat?.health();
}

The mocked version uses the Elasticsearch mock instead of the actual client:

/** @type {import('@elastic/elasticsearch').Client} */
let client = null;

module.exports.init = () => {
    console.log('Mocked init...')
    return new Promise((resolve, reject) => {
        client = new Client({
            node: 'http://localhost:9200',
            Connection: mock.getConnection()
        })
        resolve('Client initialize successfully');
    });
}

With the rewiremock initialised in a separate module

const rewiremock = require('rewiremock/node');
rewiremock.overrideEntryPoint(module); 
module.exports = rewiremock;

My test file looks as follows:

const { expect } = require('chai');
const rewiremock = require('./mock/rewiremock.js');
const storageMock = require('./mock/storage.js');

rewiremock.enable();
rewiremock('../storage.js')
    .callThrough()
    .with(storageMock);

const storage = require('../storage.js');

describe('Storage test', () => {
    before(async () => {
        await storage.init();
    })

    it(`Health test`, async () => {
        const health = await storage.health();
        console.log(`storage.health: ${health}`);
        expect(health).to.be.equal('mocked health');
    });
});

When the module.exports.health is not implemented in the mock, the test fails

  Storage test
Mocked init...
Actual health...
storage.health: undefined
    1) Health test

  0 passing (7ms)
  1 failing

But If I implement the health function it works fine:

module.exports.health = async () => {
    console.log('Mocked health...');
    return await client?.cat?.health();
}
  Storage test
Mocked init...
Mocked health...
storage.health: mocked health
    ✔ Health test

What I would like to achieve is to mock nothing but the init function, which is replacing the original ES client with the mocked one.
It seems that unless I implement a method inside my stub, rewiremock is using the client variable from the scope of the original module.

In other words, I can see that the init function is mocked successfully. I have no issue that the original health function is called - that's the desired behavior. What I expect is that the mocked init sets the client in the scope of the original module.

May I ask for some help? Is it even possible?

@theKashey
Copy link
Owner

  • First - it is more than possible and callThrough should handled it.
  • Second - you probably want to move init function to a separate file and mock it, so the "original" file still holding .health will have access to client, but the client will be mocked.
  • Thirdly - you don't have to use dependency injection for this case, consider adding ability to "inject" client from the outside, so one will be able to initialize it from the outside.

In any case, can you do console.log(storage), wondering what is inside this object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants