GithubHelp home page GithubHelp logo

iambumblehead / esmock Goto Github PK

View Code? Open in Web Editor NEW
180.0 5.0 17.0 715 KB

ESM import and globals mocking for unit tests

License: ISC License

JavaScript 92.68% TypeScript 7.32%
mocking unit-test esm es6 import test mock mocking-modules module fetch

esmock's Introduction

+                                                ██╗
+ ██████╗  ███████╗ █████═████╗  ██████╗  ██████╗██║   ██╗
+██╔═══██╗██╔═════╝██╔══██╔══██╗██╔═══██╗██╔════╝██║  ██╔╝
+████████║╚██████╗ ██║  ██║  ██║██║   ██║██║     ██████╔╝
+██╔═════╝ ╚════██╗██║  ██║  ██║██║   ██║██║     ██╔══██╗
+╚███████╗███████╔╝██║  ██║  ██║╚██████╔╝╚██████╗██║  ╚██╗
+ ╚══════╝╚══════╝ ╚═╝  ╚═╝  ╚═╝ ╚═════╝  ╚═════╝╚═╝   ╚═╝

npm coverage install size downloads

esmock provides native ESM import and globals mocking for unit tests. Use examples below as a quick-start guide, see the descriptive and friendly esmock guide here, or browse esmock's test runner examples.

Note: For versions of node prior to v20.6.0, "--loader" command line arguments must be used with esmock as demonstrated in the wiki. Current versions of node do not require "--loader".

Note: Typescript loaders ts-node 👍 and tsm 👍 are compatible with other loaders, including esmock. swc 👎 and tsx 👎 are demonstrated as incompatible with other loaders, including esmock.

esmock has the below signature

await esmock(
  './to/module.js', // path to target module being tested
  { ...childmocks }, // mock definitions imported by target module
  { ...globalmocks }) // mock definitions imported everywhere

esmock examples

import test from 'node:test'
import assert from 'node:assert'
import esmock from 'esmock'

test('package, alias and local file mocks', async () => {
  const cookup = await esmock('../src/cookup.js', {
    addpkg: (a, b) => a + b,
    '#icon': { coffee: '☕', bacon: '🥓' },
    '../src/breakfast.js': {
      default: () => ['coffee', 'bacon'],
      addSalt: meal => meal + '🧂'
    }
  })

  assert.equal(cookup('breakfast'), '☕🥓🧂')
})

test('full import tree mocks —third param', async () => {
  const { getFile } = await esmock('../src/main.js', {}, {
    // mocks *every* fs.readFileSync inside the import tree
    fs: { readFileSync: () => 'returned to 🌲 every caller in the tree' }
  })

  assert.equal(getFile(), 'returned to 🌲 every caller in the tree')
})

test('mock fetch, Date, setTimeout and any globals', async () => {
  // https://github.com/iambumblehead/esmock/wiki#call-esmock-globals
  const { userCount } = await esmock('../Users.js', {
    '../req.js': await esmock('../req.js', {
      import: { // define globals like 'fetch' on the import namespace
        fetch: async () => ({
          status: 200,
          json: async () => [['jim','😄'],['jen','😊']]
        })
      }
    })
  })

  assert.equal(await userCount(), 2)
})

test('mocks "await import()" using esmock.p', async () => {
  // using esmock.p, mock definitions are kept in cache
  const doAwaitImport = await esmock.p('../awaitImportLint.js', {
    eslint: { ESLint: cfg => cfg }
  })

  // mock definition is returned from cache, when import is called
  assert.equal(await doAwaitImport('cfg🛠️'), 'cfg🛠️')
  // a bit more info are found in the wiki guide
})

test('esmock.strict mocks', async () => {
  // replace original module definitions and do not merge them
  const pathWrapper = await esmock.strict('../src/pathWrapper.js', {
    path: { dirname: () => '/path/to/file' }
  })

  // error, because "path" mock above does not define path.basename
  assert.rejects(() => pathWrapper.basename('/dog.🐶.png'), {
    name: 'TypeError',
    message: 'path.basename is not a function'
  })
})

esmock's People

Contributors

ahmed-hritani avatar aladdin-add avatar altearius avatar bumblehead avatar iambumblehead avatar iconic-engine-hito avatar jakebailey avatar jsejcksn avatar koshic avatar swivelgames avatar tommy-mitchell avatar tripodsan avatar uwinkelvos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

esmock's Issues

follow up on PR review suggestions

esmock.d.ts export incorrect

I tried importing esmock and got:

This module is declared with using 'export =', and can only be used with a default import when using the 'allowSyntheticDefaultImports' flag.

It looks like this d.ts file is handwritten; rather than what it currently says:

declare function esmock(path: string, localmock?: any, globalmock?: any): any;
export = esmock;

I believe this should actually read:

declare function esmock(path: string, localmock?: any, globalmock?: any): any;
export default esmock;

Which matches what TS would output for the code you've written in your js file. Playground Link

Node 18 throws ERR_LOADER_CHAIN_INCOMPLETE

More details to follow shortly!

  Error {
    code: 'ERR_LOADER_CHAIN_INCOMPLETE',
    message: '"./src/esmockLoader.mjs \'load\'" did not call the next hook in its chain and did not explicitly signal a short circuit. If this is intentional, include `shortCircuit: true` in the hook\'s return.',
  }

Error: modulePath not found: "../../lib/init/config-file.js"

the gh actions passed, but we've got the error when releasing.
job log: https://jenkins.eslint.org/job/Releases/job/eslint-create-config%20Release/1/console

I just noticed there is a space(%20) in the dirname. maybe this is the cause?

     Error: modulePath not found: "../../lib/init/config-file.js"
      at esmockModuleMock (file:///var/lib/jenkins/workspace/Releases/eslint-create-config%20Release/create-config/node_modules/esmock/src/esmockModule.js:152:11)
      at async esmock (file:///var/lib/jenkins/workspace/Releases/eslint-create-config%20Release/create-config/node_modules/esmock/src/esmock.js:20:25)
      at async Context.<anonymous> (file:///var/lib/jenkins/workspace/Releases/eslint-create-config%20Release/create-config/tests/init/config-file.js:82:43)

TypeError: Cannot add property default, object is not extensible

with the upgrade to 1.8, I get:

TypeError: Cannot add property default, object is not extensible
    at x (file:///.../node_modules/esmock/src/esmockModule.js:1:802)
return!c&&w(n.default)&&(n.default.default=n.default)
  • trying to mock @aws-sdk/client-sns
  • with 1.7.8, it still works fine. also note that I use a partial mock.
  • node v16.16

Can't get esmock to work

Hi there, I'm probably using esmock wrong, but I can't seem to get it to work. I'm trying to test an express app with jest and supertest, but since jest doesn't support esm I turned to esmock for my mocking needs.

Here's my project layout:
image

So I came up with the following:

const app = await esmock.px('../../../../src/app.js', {
  '../../../../src/routes/v1/index.js': {
    '../../../../src/routes/v1/purchaseOrders.js': {
      '../../../../src/services/v1/interfaces.js': {
        getByEnvelopeTableName: async (envelopeTableName) => {
          return new Interface(1234, 4321, envelopeTableName);
        },
      },
    },
  },
});

The interfaces.js service contains a single exported async function that calls a database, that's the one I'm trying to mock. That function is imported in the purchaseOrders.js router like this:

import { getByEnvelopeTableName as getInterfaceByEnvelopeTableName } from '../../services/v1/interfaces.js';

However when I debug my test, I see that the original function is still there instead of being mocked. Do you have any idea where I might be missing something?

Can I edit existing functions with esmock?

Say I have a module

./to/module.js

export function add(a, b) { return a+b} 

and then another that uses esmock

import esmock from 'esmock';
await esmock('./to/module.js', {add: () => console.log('hello')}, globalmocks)

Will this too work? Or is esmock just mocking imports?

Seems to fail with pg

const client = await esmock(
  '../src/client.ts',
  {
    'pg': {
      Pool: (config:any) => {
      },
    },
  },
);

Results in a

Error: not a valid path: "pg" (used by
.../test/client.ts)

What about Yarn PnP support?

Any loader used in PnP mode can't resolve/load any packages except Node builtins. Yep, we have discussion in Node community how to improve loaders, how to redesign API, etc.

But now only one way - to have bundled loader. It works perfectly for me (I use simple esdbuild-based transpiler for .ts, as an example), but I can't use esmock 'out of box' (proxy bundle is the way, but...) to try node native test runner instead of Jest.

Can we remove single external dependency, 'resolvewithplus' here? Or, mark it optional and use import.meta.resolve (by flag / different import path like esmock/native or so on).

Partial mocking not working as expected

Hi,
I'm trying to partially mock an imported module, but esmock seems to mock the whole module so that functions which I don't want to mock are not available.

app.js:

import express from "express";
import passport from "passport";

const app = express();
const bearerOptions = { ... };

passport.use(bearerStrategy);
app.use(passport.initialize());

app.test.js:

import chai from "chai";
import chaiHttp from "chai-http";
import esmock from "esmock";

chai.use(chaiHttp);
chai.should();

const app = await esmock(
	"../src/app.js",
	{
		passport: {
			use: function (bearerStrategy) {
				console.log(bearerStrategy);
			},
		},
	},
	{}
);

describe("/", function () {
	it("should work", (done) => {
		try {
			chai
				.request(app.default)
				.get("/")
				.end((err, res) => {
					try {
						res.should.have.status(200);
					} catch (err) {
						console.error(res.text);
						throw err;
					}
					done();
				});
		} catch (e) {
			console.log(e);
		}
	});
});

Expected result:
Output from mocked passport.use method and call of original passport.initialize method

Actual result:
TypeError: passport.initialize is not a function at file:///C:/../src/app.js?esmk=1:9:18 at ModuleJob.run (node:internal/modules/esm/module_job:193:25) at async Promise.all (index 0) at async ESMLoader.import (node:internal/modules/esm/loader:530:24) at async file:///C:/.../node_modules/esmock/src/esmock.js:3:176 at async file:///C:/.../tests/app.test.js:11:13

What am I doing wrong here?

Thanks!

esmock fails if source maps are used

I have a TS project using ava on TS's output. I have to do this as my project calls into import.meta.resolve / import-meta-resolve, and I want to test on my project as it ships to users. I have source maps enabled so that I can debug / run coverage on my tests.

Unfortunately, esmock doesn't seem to work if source maps are enabled.

Here's a minimal repro: https://github.com/jakebailey/esmock-source-map-bug

I suspect it's due to this:

const calleePath = (err || new Error).stack.split('\n')[2]

If source maps have been loaded, the backtrace will show the mapped location (my TS src dir), not the true location (a dist dir of compiled code). Maybe esmock's API should accept a parent parameter so that the caller can pass import.meta.url, similar to import-meta-resolve or other ESM-y APIs?

How to mock file internal declare class

Hi, I want to mock some class declared on file. but it not working.
file: hasSomeClass.js

import { A } from "./a.js";
export class AA extends A {}

export class B {
  constructor() {}

  print() {
    throw "class B";
  }
}

file: hasSomeBug.js

import t from "tap";
import esmock from "esmock";

t.test("esmock can work", async (t) => {
  const { AA } = await esmock("./hasSomeClass.js", {
    "./a.js": {
      A: class A {
        print() {
          console.log("mock A");
        }
      },
    },
  });
  new AA().print();
});

t.test("esmock not work", async (t) => {
  const { B } = await esmock("./hasSomeClass.js", {
    B: class B {
      print() {
        console.log("mock B");
      }
    },
  });
  new B().print();
});

file: a.js

export class A {
  constructor() {}

  print() {
    throw "class A";
  }
}

exec on terminal

➭ node --loader=esmock hasSomeBug.js
(node:198287) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
TAP version 13
mock A
# Subtest: esmock can work
    1..0
ok 1 - esmock can work # time=10.723ms

# Subtest: esmock not work
    not ok 1 - not a valid path: "B" (used by ./hasSomeBug.js)
      ---
      stack: >
        m
        (file:///node_modules/esmock/src/esmockModule.js:1:1870)
      
        I (file:///node_modules/esmock/src/esmockModule.js:1:2075)
      
        r (file:///node_modules/esmock/src/esmock.js:2:197)
      
        Test.<anonymous> (file://hasSomeBug.js:18:23)
      at:
        line: 1
        column: 1870
        file: file:///node_modules/esmock/src/esmockModule.js
        function: m
      tapCaught: returnedPromiseRejection
      test: esmock not work
      ...
    
    1..1
    # failed 1 test
not ok 2 - esmock not work # time=16.151ms

1..2
# failed 1 of 2 tests
# time=29.79ms

environment:
nodejs version v18.8.0
test framework tap ^16.3.0

relateive issue:
#137

Unable to mock the native node:zlib module with TypeScript and Mocha/Chai/Sinon.

Hey!

I am currently working on a project using Node 18, TypeScript 4.8, purely ESM written using .mts files and the Mocha + Chai + Sinon test framework.

I have a very simple code to test that sets a header to a ServerResponse object and then pipes it through zlib to enable Gzip compression.

The code is as follows:

// import type { IncomingMessage } from "http";
import { ServerResponse } from "node:http";
import { createGzip } from "node:zlib";
import type { Gzip } from "node:zlib";
import type { HTTPStatusCodeEnum } from "../HTTP/HTTPStatusCodeEnum.mjs";

class Response extends ServerResponse
{
	private content: string = "";

	/**
	 * send
	 */
	public send(content?: string|Buffer|undefined): void
	{
		this.setHeader("Content-Encoding", "gzip");

		console.debug("called");

		// @TODO: Make response compression great again
		const ENCODER: Gzip = createGzip();
		ENCODER.pipe(this);
		ENCODER.write(content ?? this.content);
		ENCODER.end();
	}

	/**
	 * getContent
	 */
	public getContent(): string
	{
		return this.content;
	}

	/**
	 * setContent
	 */
	public setContent(content: string): void
	{
		this.content = content;
	}

	/**
	 * setStatusCode
	 */
	public setStatusCode(status_code: HTTPStatusCodeEnum): void
	{
		this.statusCode = status_code;
	}
}

export { Response };

This is part of a class Response that extends ServerResponse. The class does work as intended and has been used successfully even in production.

I wanted to rewrite my entire test base to use the configuration I explained before.
I then realized that ES modules cannot be stubbed using Sinon since that are locked.
I stumbled upon this library that seemed to be exactly what I needed. I briefly considered Proxyquire but it seems to only work with require and nothing else.

Here is my .mocharc.json file:

{
  "extension": [
    "ts"
  ],
  "node-option": [
    "experimental-specifier-resolution=node",
    "loader=ts-node/esm",
    "loader=esmock"
  ],
  "spec": [
    "__tests__/**/*.spec.mts"
  ],
  "timeout": 5000,
  "parallel": true,
  "checkLeaks": true,
  "diff": true,
  "forbidPending": true
}

Here is the tsconfig.json file used for testing:

{
	"compilerOptions": {
		"outDir": "build",
		"allowJs": false,
		"target": "ESNext",
		"alwaysStrict": true,
		"removeComments": true,
		"strict": true,
		"charset": "UTF-8",
		"noImplicitAny": true,
		"noImplicitReturns": true,
		"noImplicitThis": true,
		"strictNullChecks": true,
		"strictFunctionTypes": true,
		"allowUnusedLabels": false,
		"module": "NodeNext",
		"allowUnreachableCode": false,
		"noUnusedLocals": true,
		"noUnusedParameters": true,
		"newLine": "LF",
		"moduleResolution": "NodeNext",
		"useUnknownInCatchVariables": true,
		"lib": ["ESNext"],
		"noUncheckedIndexedAccess": true,
		"strictPropertyInitialization": true,
		"strictBindCallApply": true,
		"forceConsistentCasingInFileNames": true,
		"listFiles": false,
		"listEmittedFiles": true,
		"noErrorTruncation": true,
		"noFallthroughCasesInSwitch": true,
		"noImplicitOverride": true,
		"noPropertyAccessFromIndexSignature": true,
		"pretty": true,
		"sourceMap": true
	},
	"include": [".", "./**/.*", "./**/*.json"],
	"ts-node": {
	  "files": true,
	  "swc": true
	}
}

Here is the test that I wrote:

/* eslint-disable @typescript-eslint/no-unused-expressions, @typescript-eslint/no-empty-function, max-len, max-lines-per-function */

import { IncomingMessage } from "node:http";
import { Socket } from "node:net";
import { expect } from "chai";
import esmock from "esmock";
import { Response } from "../../../../src/main/Web/Server/Response.mjs";


await esmock(
	"../../../../src/main/Web/Server/Response.mts",
	import.meta.url,
	{
		zlib: {
			createGzip: {
				pipe: () =>
				{
					console.debug("pipe called 2");
				}
			}
		}
	}
);

describe(
	"Response",
	(): void =>
	{
		describe(
			"send",
			(): void =>
			{
				it(
					"should call the Gzip.pipe functions from the zlib native module",
					(): void =>
					{
						const SOCKET: Socket = new Socket();
						const INCOMING_MESSAGE: IncomingMessage = new IncomingMessage(SOCKET);
						const RESPONSE: Response = new Response(INCOMING_MESSAGE);

						RESPONSE.send("Test");

						expect(PIPE_STUB.called).to.be.true; // Irrelevant as I previously tried to use SinonStubs (which are still in the test but I removed them from this sample since they are never called anywhere. This expect will always fail but this is not my problem.
					}
				);
			}
		);
	}
);

The thing is, whilst running the test, it properly prints "called" from the send method, but it never prints "pipe called 2" from the mocked module.

I have been scratching my head why for some time now and I have honestly no idea what's going on. I do not get any error from esmock with this syntax. If I change the module path to use the .mjs extension I get an invalid module id error. This means the .mts extension syntax seems to be correct but somehow, the module simply isn't mocked.

I believe I am missing something here. Could you help me solve this issue please? 🙏

Thanks! ❤️

Unable to mock relative TS imports [jest+ts]

Hi

So I'm wanting to mock an exported function from a file that is called from my code to test.

So the code to test:

// src/lib/utils/logUtils.ts
import { sendErrorToSentry } from './debugUtils'

export function logError(err: Error): void {
   sendErrorToSentry(error)
}

That code makes use of this which I want to mock:

// src/lib/utils/debugUtils.ts
export const sendErrorToSentry = (error: Error): void => {
  // Stuff I want to mock
  console.log('ORIGINAL)
}

And how I test (Jest)

import { jest } from '@jest/globals'
import esmock from 'esmock'

it('logError() sends errors to Sentry', async () => {
  const mockLogSentry = jest.fn()
  const mockLog = await esmock(
    '/full/path/src/lib/utils/logUtils',
    {
      '/full/path/src/lib/utils/debugUtils.js': {
        sendErrorToSentry: mockLogSentry
      }
    }
  )

  mockLog.logError(new Error())
  expect(mockLogSentry).toHaveBeenCalled()

})

I am using running my code in TS transpiled to ESM (so type: module) with:

"esmock": "^2.0.1",
"jest": "29.0.0",
"typescript": "^4.8.3"

And calling my test with:

"node --loader=esmock --experimental-vm-modules ./node_modules/.bin/jest"

I've tried a few different scenarios but none of them will mock - the console always prints "ORIGINAL" and my spy never gets called. I looked at the TS example but it doesn't make use of local TS files and I wonder if that's where the issue resides.

Do you have any thoughts on this?

Error: not a valid path: "got"

Details

Facing error only when I tried to mock got

Error: not a valid path: "got" (used by ./index.test.js)
      at esmockModulesCreate (file:///home/node-test/node_modules/esmock/src/esmockModule.js:126:11)
      at esmockModulesCreate (file:///home/node-test/node_modules/esmock/src/esmockModule.js:139:10)
      at async esmockModuleMock (file:///home/node-test/node_modules/esmock/src/esmockModule.js:146:28)
      at async esmock (file:///home/node-test/node_modules/esmock/src/esmock.js:20:25)
      at async makeSut (file:///home/node-test/index.test.js:8:10)
      at async Context.<anonymous> (file:///home/node-test/index.test.js:38:19)

Code samples:

// index.js
import fs from 'fs';
import got from 'got';

export const dImport = () => {
  const json = JSON.parse(
    fs.readFileSync(
      new URL('./index.json', import.meta.url)
    )
  )
  return json
}

export const getData = async () => {
  return got.get('https://pokeapi.co/api/v2/pokemon/ditto', { responseType: 'json' })
    .then(response => {
      console.log(response)
    })
}
// index.test.js
import chai from 'chai'
import esmock from 'esmock'

const makeSut = async () => {
  return await esmock('./index.js', {
    fs: {
      readFileSync: () => {
        return JSON.stringify({ name: "node" })
      }
    },
    got: {
      get: async() => {
        return { data: 'ok' }
      }
    }
  })
}

describe('test index', () => {
  it('dImport should return correct value', async () => {
    const utils = await makeSut()
    assert.deepEqual(utils.dImport(), { name: "node" })
  })
})

Current behavior

Facing not a valid path: "got" when try to mock got

Expected behavior

esmock should mock got

Environments

Ubuntu 18
Node 16.13.0
NPM 8.1.0

Thank you!

Mocked dependency still gets evaluated

I have the following beforeEach hook in a mocha test suite:

beforeEach(async function() {
	execa = sinon.stub().resolves();
	({ update } = await esmock('../../src/init/update.js', {}, {
		execa: { execa },
		'../../src/init/config.js': {
			default: config,
			paths
		}
	}));
});

However, file config.js still gets evaluated - if I insert a console.log statement I can see the output. This is not intended behavior, or is it? At least, this never happened until recently. Might it have something to do with the fix to #49? I am on NodeJS 17.9.0 and esmock 1.7.5.

EDIT:
My bad for the part about this being new. That was actually on me...
Still, I'm a little surprised to see config.js gets loaded. I thought the point mocking it was that it wouldn't?

reduce size of typescript types file [need help]

When the package is published to npm, these filesizes are listed,

750B  LICENSE              
4.4kB README.md            
845B  package.json         
4.8kB src/esmock.d.ts      
824B  src/esmock.js        
129B  src/esmockArgs.js    
423B  src/esmockCache.js   
32B   src/esmockDummy.js   
125B  src/esmockIsLoader.js
1.6kB src/esmockLoader.js  
2.2kB src/esmockModule.js

Total size from all files is 16.1kB and 4.4kB is 27% of 16.1kB. The types file has multiple repeated sections in it.

ideas to speed things up

  • cache resolved module paths,
  • try dummy cjs modules
  • store reference to _load stage live modules
  • use querystring mock paths

Minified sources in esmock package

It's not a feature request (yet), but only question - why?

From my understanding, JS ecosystem provides the greatest debugging ability - everything is script, and you can easily inspect / modify installed packages without IDA. And browser-like minification totally breaks it.

Package download size? No - packages published as archives with good text compression out of the box.
Package disk usage? May be, but cute readme with ascii art already takes 4k )

Performance? Perhaps, but in the most real-world scenarios it's only premature optimization (which is may be much more evil than real performance problems, you know). Especially for small, one-time loaded scripts like Node loaders.

image

Fortunately, I can just copy source code directly from github )

Partial mocking of module under test

Let's consider a scenario when we want to have a number of methods within a module replaced by mocks but want to test a specific method and it's interactions and don't want to extract these helper methods into different javascript file.

Is that something achievable with current version of esmock ?

Example:

export async function handler() {
  doBark();
}

async function doBark() {
  return "Wooof!"
}

And test:

import esmock from "esmock";

const mocks = {}

mocks['./foo.js'] = {
    otherFn: mockMethod
}

const mockMethod = jest.fn()

const foo = await esmock("./foo.js", mocks)

describe("Foo", () => {
    it("Foo defined", () => {
        expect(foo).toBeDefined();
    })


    it("Mock out internal function", async () => {

        await foo.some()
        expect(mockMethod).toHaveBeenCalled()
    })

})

document or catch this error

SyntaxError {
  message: 'The requested module \'./the-module.mjs\' does not provide an export named \'the-named-export\'',
}

this happens when the target module imports named values from a mocked module, where the mock is not a partial mock and is missing the definition reported in the error

probably this error should be caught and rethrown with a message recommending a solution: define the missing name on the mock definition or use a partial mock

small corrections

package description here is wrong, should describe jest not tsm https://github.com/iambumblehead/esmock/blob/master/tests/tests-jest/package.json#L3

test descriptions here are wrong, should be updated https://github.com/iambumblehead/esmock/blob/master/tests/tests-node/esmock.node.test.js#L396-L435

modulePath here should be renamed moduleId https://github.com/iambumblehead/esmock/blob/master/src/esmockModule.js#L150

invalid comma here should be removed from readme json example https://github.com/iambumblehead/esmock/blob/master/README.md?plain=1#L37

Support for NPM Workspaces

This is a bit of a weird one. I've noticed that there are certain scenarios where esmock fails with NPM's Workspaces.

I'm not entirely sure where to start with this particular issue, though. But I went ahead and created an example repo that reproduces the issue.

If you clone the repo, you can reproduce the issue by running the following:

npm run setup
npm run test

If you're not on a UNIX-based system, you can run the commands manually:

git submodule init
git submodule update
cd esmock
git apply ../add-tests.patch
cd ..
npm install
npm run --workspace=esmock test

Notice the following failure:

    # Subtest: should mock a node_module
    not ok 17 - should mock a node_module
      ---
      duration_ms: 0.028361695
      failureType: 'testCodeFailure'
      error: |-
        Expected values to be strictly equal:
        + actual - expected

        + 'not_mocked'
        - 'foobar'
      code: 'ERR_ASSERTION'
      stack: |-
        TestContext.<anonymous> (file:///home/****/src/by-project/esmock-ws/esmock/spec/node/esmock.node.test.js:226:10)
        async Test.run (node:internal/test_runner/test:338:9)
        async Test.processPendingSubtests (node:internal/test_runner/test:158:7)
      ...

notes to consider adding minification to the publish step

here are the results from running a command like this on files in src

$ npx esbuild ./src/esmockModule.js --minify --outfile=./mini/esmockModule.js

$ ls -l ./src
total 56
-rw-r--r--  1 bumble  staff    96B Jul 19 13:36 esmock.d.ts
-rw-r--r--  1 bumble  staff   1.5K Jul 19 13:36 esmock.js
-rw-r--r--  1 bumble  staff   888B Jul 19 13:35 esmockCache.js
-rw-r--r--  1 bumble  staff   209B Jul 19 13:36 esmockIsLoader.js
-rw-r--r--  1 bumble  staff   3.6K Jul 19 13:43 esmockLoader.mjs
-rw-r--r--  1 bumble  staff   6.5K Jul 19 13:35 esmockModule.js

$ ls -l ./mini 
total 32
-rw-r--r--  1 bumble  staff   731B Jul 20 09:14 esmock.js
-rw-r--r--  1 bumble  staff   427B Jul 20 09:14 esmockCache.js
-rw-r--r--  1 bumble  staff   184B Jul 20 09:14 esmockIsLoader.js
-rw-r--r--  1 bumble  staff   2.5K Jul 20 09:15 esmockModule.js
name src mini difference
esmock.js 1500 731 769
esmockCache.js 888 427 661
esmockLoader.js 209 184 25
esmockModule.js 6500 2500 4000
total 5455

This micro-optimisation could reduce final download size from 31.8kb to, possibly, around ~26.3kb. Perhaps the size could be reduced furter If the loader and resolvewith package were minified. Its also possible the final package size would not change much, if minification reduces the effectiveness of tar compression used by npm.

make partial mocks an optional thing rather than default thing

the situation reported in this ticket should not happen unless it is specified somehow #51

so instead of something like this

const originalFile = await import( pathtooriginalfile );
const mockedFile = Object.assign({}, originalFile, mockDefinitions);

return mockedFile

we want something like this to happen instead

const originalFile = isPartialMockEnabled && await import( pathtooriginalfile );
const mockedFile = Object.assign({}, originalFile, mockDefinitions);

return mockedFile

Error: invalid moduleId when trying to stub a module dependency ES6

I have created 2 files add.js and server.js. The files are as follows

add.js

const add = () => {
    return 10
}

export { add }

server.js

import { add } from './add.js'

function print() {
    console.log(add())
}

export {print}

I have written a unit test file for server.js as follows

import { expect } from 'chai'
import esmock from 'esmock'
import sinon from 'sinon'

describe('server.js', () => {
    const addStub = sinon.stub()
    const m = esmock('../server.js', {
        ['./add.js']: {
            add: addStub
        }
    })

    it ('should work', () => {
        console.log(m)
        m.print()
        expect(2).be.equal(2)
    })
})

I have used a library called esmock which works similarly to proxyquire. But when I ran the test using mocha. I got the following error

Error: invalid moduleId: "./add.js"
I am wondering what could be the issue here. Is the problem related to the path specified in esmock or the way I am acquiring the import?

FYI: esmock with same syntax works well for standard imports from npm like axios or aws-sdk

I was hoping someone could help me figure out whats the problem. Thanks in advance!

Imports Aren't Mocked; Using Mocha, Sinon, Assert

Versions:

  • node: v16.7.0
  • esmock: v0.3.9
  • mocha: v9.0.3
  • sinon: v11.1.2
  • c8: v7.8.0

After running npm i, run: npm run test to get the error.
I'm attempting to mock an import, but it doesn't seem to be doing anything. I've tried everything, including just setting the default mock to return class {}, and it still throws the same error.

Here's a repo that can consistently reproduce the issue: https://github.com/Swivelgames/_esmock_issues_13

The error tells me that it's actually doing something (unlike most of the other mock libraries I've come across that claim ESM support), which is great! And if I can get passed this issue, I should be home free!

> [email protected] test
> npx mocha --loader=esmock --config ./.mocharc.jsonc

(node:1137639) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)


  MyClassWrapper
    constructor()
      1) should mock imports


  0 passing (15ms)
  1 failing

  1) MyClassWrapper
       constructor()
         should mock imports:
     TypeError: MyClass is not a constructor
      at new MyClassWrapper (file:///******/src/index.js?key=1?esmockGlobals=null#esmockModuleKeys=file:///******/src/class.js?esmockKey=1&esmockModuleKey=./class.js&isesm=true&exportNames=default:5:20)
      at Context.<anonymous> (file:///******/src/index.test.js:23:16)

This is probably more like a support post than an actual issue. But, any thoughts?

Can't mock package 'vue' if it doesn't installed

My case: I have generated package like below:

import {h} from 'vue';
export const ComponentName = () => h('svg', { /* some properties */ });

And I want to test this package. The point of the test is just to check that function h was called with the correct parameters.

So I tried mock h function from vue

// test.spec.js
const component= await esmock(`../component.js`, {}, {
  vue: {
    h: (...args) => args
  }
})

It works great. But, only if the vue is installed as a dependency. Because in reality no vue functionality is used anywhere, I tried to remove this dependency, but then I get the error:

not a valid path: "vue" 

What can I do about it? I wouldn't want to install an entire framework just to mock a single function in tests.

You can reproduce the problem

  1. Clone repo: git clone https://github.com/cawa-93/iconify-prerendered.git
  2. Delete vue dependency: npm un vue
  3. Install others dependencies: npm ci
  4. Generate code for testing: npm run build
  5. Run test: npm run test

How to mock class ?

Let's consider following source file:

import { SecretsManager} from "@aws-sdk/client-secrets-manager";

const { REGION, SECRET_ID } = process.env;

const secretsManager = new SecretsManager({ region: REGION });

export async function handler () {
    return await secretsManager.listSecrets()
}

And unit test for it:

import esmock from "esmock";
import {jest} from "@jest/globals";

const mocks = {}
const awsSdk = `@aws-sdk/client-secrets-manager`
mocks[awsSdk] = {
    SecretsManager: {
       constructor: jest.fn()
    }

}

const foo = await esmock("./foo.js", mocks)

describe("Foo", () => {
    it("Foo defined", () => {
        expect(foo).toBeDefined();
    })

    it("List secrets called on handler", async () => {

        await foo.handler()
    })
})

Unfortunately secrets manager when instantiated with constructor is not mocked, but presented as actual class.
What is the guideline how to mock it ? Is there additional information that I can provide to diagnose ?

Please forgive me if SecretsManager source is coming from typescript and it seems that by default it is resolving to dist-cjs source files :/

I am invoking jest with following:
NODE_OPTIONS="--loader=esmock --experimental-vm-modules" jest --config jest.config.cjs

Node 16.12+ breaks esmock

When upgrading to latest node, we're seeing errors with using esmock.

With some investigation, it looks like 16.12 is the version that broke esmock.

Node 16.12 CHANGELOG

How to reproduce

  1. Check out this repository
  2. nvm use 16.11
  3. npm ci
  4. npm test
  5. Observe tests pass
  6. nvm use 16.12
  7. npm ci
  8. npm test
  9. Observe many test failures

Error when mocking core modules with the `node:` prefix (minor annoyance)

Context:

// someModule.js

import path from 'node:path';

...

Trying to do something like the following:

const someModule = await esmock('./someModule.js', {
    'node:path': pathMock,
});

Results in the following error:

Error: not a valid path: "node:path"

This works without changing someModule.js:

const someModule = await esmock('./someModule.js', {
    'path': pathMock,
});

However, now the module URLs in the tests and the original module are different. I've opened a PR in iambumblehead/resolvewithplus#27 to add support for the node: prefix.

support import() mocking?

we are using const eslint = await import("eslint");(as some users may haven't installed eslint), but eslint was not right mocked in this case. the source is something like:

/**
 * Writes a configuration file in JavaScript format.
 * @param {Object} config The configuration object to write.
 * @param {string} filePath The filename to write to.
 * @throws {Error} If an error occurs linting the config file contents.
 * @returns {void}
 * @private
 */
async function writeJSConfigFile(config, filePath) {
    debug(`Writing JS config file: ${filePath}`);

    let contentToWrite;
    const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })}\n`;

    try {
        // eslint-disable-next-line node/no-unsupported-features/es-syntax
        const eslint = (await import("eslint"));
        const linter = new eslint.ESLint({ baseConfig: config, fix: true, useEslintrc: false });
        const result = await linter.lintText(stringifiedContent);

        contentToWrite = result[0].output || stringifiedContent;
    } catch (e) {
        debug("Error linting JavaScript config file, writing unlinted version");
        const errorMessage = e.message;

        contentToWrite = stringifiedContent;
        e.message = "An error occurred while generating your JavaScript config file. ";
        e.message += "A config file was still generated, but the config file itself may not follow your linting rules.";
        e.message += `\nError: ${errorMessage}`;
        throw e;
    } finally {
        fs.writeFileSync(filePath, contentToWrite, "utf8");
    }
}

Strict mocking isn't working as expected

I've noticed two problems with strict mocking:

  1. Given a dependency that provides a default export, when I call esmock in strict mode on a dependent module and I don't define a default export for the mocked dependency, I expect esmock to fail due to the missing default export. What actually happens is that it sets the entire mock definition object to the value of the default export.
  2. When I call esmock in strict mode on a module and I don't include a mock of one of its dependencies, I expect esmock to fail due to trying to import a missing dependency. What actually happens is that the original module is imported, and production code is executed (I would expect this behavior in partial mocking)

I've made an example gist: https://gist.github.com/gmahomarf/c60e05ef57c35aad8e18590df8e3f945

Issue with partially mocking module that is also used within same file by another module

Hello.

I think I've found an issue. I'm having trouble with partially mocking deeply nested module that is also used in same file. Example below. I'm on Mocha and Node 17.4.0.

const supertestModule = await esmock.px('../modules/supertest.js', null, {
  '../../../../server/troublesomeFile.js': {
    funcOne: async () => {
      console.log('Ran')
    }
  }
})

troublesomeFile.js

export async function funcOne() {
  console.log('Should not be called')
}

export async function funcTwoSameFile() {
   const test = await funcOne()
  
  // some more code
  
  return test
}

supertest.js (might not be relevant)

// imports app.js...
export default async () => {
     // More code...

     return supertest(app)
      .post(`/v1/${endpoint}`)
      .set({ ...thingsToSet })
      .send({ ...requestBody })
  }
}

When funcTwoSameFile is called somewhere deeply nested in app.js, original version of funcOne is called and not the mocked one. Is this some kind of limitation or am i missing something? My other (full-module, not .px mocks) are working fine in other tests.

Thanks for your help!

[poll] is it a good idea?: rename 'partial mock' to 'merged' or 'blended' mock

Is it a good idea to rename 'partial mock' to 'merged' or 'blended' mock?

If you're cc'd here feel free to ignore or give an opinion whichever is fine, cc: @Swivelgames @tripodsan @aladdin-add @jakebailey

Currently, "partial mock" is described this way in the README (and the wiki link is here),

// use esmock.px to create a "partial mock"
const pathWrapPartial = await esmock.px('../src/pathWrap.js', {
  path: { dirname: () => '/home/' }
})

The words "merged", "blended" and "assigned" are more accurate imo. What do you think?

// use esmock.m to create a "merged mock" from  the original module definition
const pathWrapPartial = await esmock.m('../src/pathWrap.js', {
  path: { dirname: () => '/home/' }
})

The interface could be changed when the next major release happens and, if no one comments, the interface will probably be changed.

cc @vueme who is also using the "px" interface. Please feel free to give any opinion you might have.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.