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] Is it possible to mock indirect imports? #300

Closed
bosschaert opened this issue Apr 26, 2024 · 10 comments
Closed

[Question] Is it possible to mock indirect imports? #300

bosschaert opened this issue Apr 26, 2024 · 10 comments

Comments

@bosschaert
Copy link

Lets say my JS source has function.js:

import { depFun } from '../more-function.js';

export const impFun = () => {
   depFun();
}

then I have more-function.js:

import { deepFun }  from './deep-function.js';

export const depFun = () => {
   return deepFun();
}

and deep-function.js:

export const deepFun = () => {
   return "deep original";
}

Then in my unit test I would like to mock the deepFun() that is indirectly imported

describe('Test test', () => {
  it('mytest', async () => {
    const { impFun } = await esmock(
      './function.js', {
        './more-function.js': {
          './deep-function.js': {
            deepFun: () => "mocked"
          }
        }
      });
    assert.equal('mocked', impFun());
  });
});

But this doesn't work. I get a SyntaxError: Unexpected token '.' at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:168:18)

Is something like this possible with esmock?

@iambumblehead
Copy link
Owner

iambumblehead commented Apr 26, 2024

do one of these work for you?

// mock deep-function.js' 'deepFun' only for
// function.js -> more-fuction.js -> deep-function.js
const { impFun } = await esmock('./function.js', {
  './more-function.js': await esmock('./more-function.js', {
    './deep-function.js': {
      deepFun: () => "mocked"
    }
  })
})
// globally mock deep-function.js' 'deepFun'
const { impFun } = await esmock('./function.js', null, {
  './deep-function.js': {
    deepFun: () => "mocked"
  }
})

@iambumblehead
Copy link
Owner

closing, feel free to reopen for any reason

@bosschaert
Copy link
Author

Thank you @iambumblehead . I can confirm that both approaches work!

@bosschaert
Copy link
Author

Hi @iambumblehead I have a slight follow up question:

Let's say I have function.js like this:

export const myFun = () => {
  return 'fun';
}

export const impFun = () => {
  return myFun();
}

And I would like to mock myFun() from it so that when I call impFun() it returns mocky. I tried this but that doesn't work. I also tried it with null as the second parameter:

      const { impFun } = await esmock('./function.js', {
        './function.js': {
          myFun: () => 'mocky'
        }
      });

     assert.equal('mocky', impFun());

It's probably a beginner's mistake 😄

@iambumblehead
Copy link
Owner

esmock cannot do that. esmock modifies import paths to import mocked definitions, and so mock definitions must be imported. This limitation allows esmock to work without usage source code transformations,

It does something like this

function.js

-import deep from './deep-function.js'
+import deep from './deep-function.js?with-mocked-definitions'

deep('is mocked')

@iambumblehead
Copy link
Owner

It's probably a beginner's mistake 😄

Probably esmock is difficult to understand. A review I found on the internet recently https://adamtuttle.codes/blog/2024/mocking-esm-dependencies-for-tests/ writes,

[esmock] seemed like it might be simpler, I couldn't get it working the way I wanted (see also: "at all") and the docs were not super clear.

Thanks for using esmock despite difficulties. esmock tries to be as useful and practical as possible.

@bosschaert
Copy link
Author

Thanks for explaining @iambumblehead I was thinking, maybe I can do it by adding a level of indirection? E.g. adding a functionimporter.js alongside that imports function.js and calls its methods. It seems similar to the other scenarios, but unfortunately I wasn't able to get it to work...

Something like this functionimporter.js:

import { impFun } from "./function.js";

export const impFunCaller = () => {
  return impFun();
}

And then use it something like this:

      const { impFunCaller } = await esmock('./funcimporter.js', {
        './function.js': {
          myFun: () => 'mocky'
        }
      });

      assert.equal('mocky', impFunCaller());

@iambumblehead
Copy link
Owner

@bosschaert this test resembling yours passes here #301

@bosschaert
Copy link
Author

Thanks for your explanations @iambumblehead now that I know the boundaries of esmock I am able to use it effectively. Kudos to the project!

@iambumblehead
Copy link
Owner

@bosschaert thanks :)

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