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

Mock called, but not stubbing dependency #139

Open
yuv-c opened this issue Aug 25, 2022 · 2 comments
Open

Mock called, but not stubbing dependency #139

yuv-c opened this issue Aug 25, 2022 · 2 comments

Comments

@yuv-c
Copy link

yuv-c commented Aug 25, 2022

I am trying to test an express API.
Upon calling one of the endpoints, the API calls the function loadFromFS. I'd like to mock its return value.
The files are as follows:

// server.js:
const { router } = require('./api');

const app = express()
...
app.use('/api', authentication, authorization('@data/accounts'), router);
...
return app
//api.js:
const { searchSourceQueries } = require('../../../aliasing-utility/aliasing-helpers');
router.get('/sources',searchSourceQueries); <-- The endpoint i'm trying to test
// aliasing-helpers.js:
const { stuff } = require('stuff...)
...
const { loadFromFS } = require('@data/utils/queries-loader'); <-- the require i'm trying to mock

console.log('loading stuff with loadFromFS');
const customersQuery = loadFromFS(`${__dirname}/sql/sources/customers`);
// other constants initialized with loadFromFS
console.log(`Customers query: ${customersQuery}`);

const foo = () => {
  do SQL stuff with the constants...
// customers.sql:
SELECT 2;
// spec-prebuild.js:
const { expect } = require('chai');
const chai = require('chai');
const chaiHttp = require('chai-http');
const _ = require('lodash');
const rewiremock = require('rewiremock/node');
...

describe('Test some API', async () => {
  let serverFile;
  const = pushMockDataToDB () => {...}
  const = clearMockDataFromDB () => {...}

  before(async () => { // This works - mocking my auth middleware
    // eslint-disable-next-line global-require
    serverFile = rewiremock.proxy(() => require('../server/src/server'), {
      '@data/utils/server-auth-client': {
        authentication: authenticationMock,
        authorization: authorizationMock
      }
    });
    rewiremock.overrideEntryPoint(module);

    await clearMockDataFromDB();
    await pushMockDataToDB();
  });

  it('Should return all sources', async () => {

    const mockLoadFromFS = path => {
      console.log('Mock called with path:', path);
      return { foo: 'hello' };
    };

    rewiremock.proxy(
      () => require(`${getAliasingUtilityDirPath()}/aliasing-helpers`),
      {
        '@data/utils/queries-loader': {
          loadFromFS: mockLoadFromFS
        }
      }
    );

    const res = await chai.request(serverFile.app).get('/api/sources');
    expect(res).to.have.status(200); // An SQL syntax error was expected
    expect(_.last(res.body)[2]).to.equal(1);

Log:

loading stuff with loadFromFS
Customers query: {"customers":"select 2;"}
Mock data pushed to DB
loading stuff with loadFromFS
Mock called with path: /.../sql/sources/not-aliased
Mock called with path: /.../sql/sources/customers
Mock called with path: /.../sql/helpers
Customers query: {"foo":"hello"} <-- Mock succeeds
Received request for endpoint "sources"

AssertionError: expected 2 to equal 1 <-- Data is fetched, when it should fail and throw an error.
Expected :1
Actual   :2

As you can see, initially the variable customersQuery, is loaded as usual by , and then mocked. Even after it is mocked, foo gets the un-mocked version of it.
If I move customersQuery to the inner scope of foo, the mock function is never called.
Any help would be appreciated.
Thank you.

Node version v14.15.4
rewiremock 3.14.3.
mocha 9.1.3

@yuv-c
Copy link
Author

yuv-c commented Aug 25, 2022

I've re-written my use of rewiremock, and am able to get it to mock properly:

serverFile = rewiremock.proxy(() => require('../server/src/server'), {
      '@data/utils/server-auth-client': {
        authentication: authenticationMock,
        authorization: authorizationMock
      },
      '@data/utils/queries-loader': {
        loadFromFS: mockLoadFromFS
      }
    });
    rewiremock.overrideEntryPoint(module);

Notice that loadFromFS isn't directly required by server.js, so it was unclear i should be mocking it from there.

I do have another question - Is it possible to proxy(() => '../server/src/server'), {mockA} inside before function, and then mock other requires used by '../server/src/server' later during the test?

@theKashey
Copy link
Owner

Hey, sorry for quite a late answer, but it's better late that never?

History note - rewiremock supports different "models" of work in order to help migration from other libraries existing before it. rewiremock.proxy is mimicking proxyquire interface.

In short proxy(file, overrides) returns you a file with overrides 🤷‍♂️ overrides. Depending on different flags it can be direct children only, or any transitive dependency required by this file anywhere.

If you want to mocks something else in the same test you need to do const newServer = .proxy stuff again.


At the same time you can change rules a little bit and mock @data/utils/queries-loader instead of '../server/src/server'

rewiremock('@data/utils/queries-loader').with({loadFromFS: mockLoadFromFS});
const serverFile = require('../server/src/server');

In this you can do the following trick

const loaderMock = rewiremock('@data/utils/queries-loader')
  .callThrough() // pass to real file
  .dynamic() // allow changes in the future
const serverFile = require('../server/src/server');
// update mocking in the future
loaderMock.with({loadFromFS: mockLoadFromFS});

Keep in mind dynamic wraps module export in Proxy to be able to update it at runtime and some operations (assigning to a constant) can bypass it. So I cannot guarantee that it gonna work for every case.

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