iambumblehead / esmock Goto Github PK
View Code? Open in Web Editor NEWESM import and globals mocking for unit tests
License: ISC License
ESM import and globals mocking for unit tests
License: ISC License
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).
[email protected]
node: v14
you can see the github action log: https://github.com/eslint/create-config/runs/7493137743?check_suite_focus=true#step:5:12
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()
})
})
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.
Fortunately, I can just copy source code directly from github )
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)
...
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.
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! โค๏ธ
Line 1 in 5dfe486
What if esmock loaded indirectly, as part of other loader?
Error: process must be started with --loader=esmock
I suggest to set flag in global object inside resolve / load, or something like that.
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.
Line 9 in 28cbda5
It allows you type mocked module like so:
const myModule = await esmock<typeof import('/module/path')>('/module/path')
does the package support node 12.x(windows-latest)?
I just got an error like Error: modulePath not found: "../../lib/init/config-file.js"
. the weird thing is it's working fine in non-windows.
the job log: https://github.com/eslint/create-config/runs/4307659622?check_suite_focus=true
currently you need to pass --loader=esmock
as an argument to node because esmock checks only process.argv.
It would be good to support NODE_OPTIONS env var too.
Importing [email protected] leads to the following error:
Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in [...]/node_modules/esmock/package.json
// @ts-ignore
import esmock from "esmock";
esmock('./foo', {]);
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?
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!
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");
}
}
With 1.9.1 I get:
> eslint .
/test/my.test.js
17:20 error Unable to resolve path to module 'esmock' import/no-unresolved
the problem are the exports
added in: 96d2754
also see:
https://nodejs.org/docs/latest-v16.x/api/packages.html#package-entry-points
when I add the
"main": "./src/esmockLoader.js",
again, it works.
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!
see: https://github.com/testdouble/quibble
It seems that the functionality is the same, isn't it?
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
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
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
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)
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)
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.
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.
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?
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
.
nvm use 16.11
npm ci
npm test
nvm use 16.12
npm ci
npm test
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" })
})
})
Facing not a valid path: "got"
when try to mock got
esmock should mock got
Ubuntu 18
Node 16.13.0
NPM 8.1.0
Thank you!
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.',
}
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?
see the log: https://github.com/eslint/create-config/runs/5709601942?check_suite_focus=true.
the weird thing is I didn't see how it is affected in node.js' changelog. (#_<-)
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
git clone https://github.com/cawa-93/iconify-prerendered.git
vue
dependency: npm un vue
npm ci
npm run build
npm run test
a special kind of glob expansion needs to be happen in the windows CI pipeline and maybe something like this could be used https://superuser.com/questions/460598/is-there-any-way-to-get-the-windows-cmd-shell-to-expand-wildcard-paths#answer-829556
I've noticed two problems with strict mocking:
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.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
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
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)
@aws-sdk/client-sns
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.
Versions:
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?
key : val
other do not key: val
'/All\ files[^\d]*(\d\d?\.?\d?\d?)/'
, stackoverflowI 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:
Line 12 in 31cc298
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?
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
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
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!
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.