{"_id":"vitest-mock-commonjs","_rev":"2-acc09f45db8920e43a14350d48c7cc73","name":"vitest-mock-commonjs","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"vitest-mock-commonjs","version":"1.0.0","keywords":["vitest","CommonJS","require","mock"],"author":{"url":"http://linkedin.com/in/jmussman","name":"Joel Mussman","email":"jmussman@wsmallrock.net"},"license":"MIT","_id":"vitest-mock-commonjs@1.0.0","maintainers":[{"name":"jmussman","email":"jmussman@smallrock.net"}],"homepage":"https://github.com/jmussman/vitest-mockrequire","bugs":{"url":"https://github.com/jmussman/vitest-mockrequire/issues"},"dist":{"shasum":"8ed8dba16d3f6c1b101458372dd23e29e60cb16d","tarball":"https://registry.npmjs.org/vitest-mock-commonjs/-/vitest-mock-commonjs-1.0.0.tgz","fileCount":9,"integrity":"sha512-+IDIzF6U34NoqvXRXSVIYAlcoq038zbmdtOGMr7SG+bay+aFCfJIWsftwaD3OUN7S7+CQYel9kd4bJtfadC/ZA==","signatures":[{"sig":"MEUCIQDsVdXZSk2aIbjAspVVbx9d2h2moPlHutWBz1zlSCi+WAIgYhTkyGHieGHe993bdEYaPaL8RuMzo6lNl16qAAvZlXc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":400428},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","gitHead":"5751ffff32d6ad21ea1c7cb0625f31c1d845f05f","_npmUser":{"name":"jmussman","email":"jmussman@smallrock.net"},"repository":{"url":"git+https://github.com/jmussman/vitest-mockrequire.git","type":"git"},"_npmVersion":"10.5.2","description":"Module loader overrides to mock code-under-test CommonJS modules from vitest","directories":{},"_nodeVersion":"20.13.1","_hasShrinkwrap":false,"devDependencies":{"vitest":"latest"},"_npmOperationalInternal":{"tmp":"tmp/vitest-mock-commonjs_1.0.0_1724707158585_0.3134566473006337","host":"s3://npm-registry-packages"}},"1.0.1":{"name":"vitest-mock-commonjs","version":"1.0.1","keywords":["vitest","CommonJS","require","mock"],"author":{"url":"http://linkedin.com/in/jmussman","name":"Joel Mussman","email":"jmussman@wsmallrock.net"},"license":"MIT","_id":"vitest-mock-commonjs@1.0.1","maintainers":[{"name":"jmussman","email":"jmussman@smallrock.net"}],"homepage":"https://github.com/jmussman/vitest-mock-commonjs","bugs":{"url":"https://github.com/jmussman/vitest-mock-commonjs/issues"},"dist":{"shasum":"97d0db5bcd2aa996dbe077fd568ccbab538d74ec","tarball":"https://registry.npmjs.org/vitest-mock-commonjs/-/vitest-mock-commonjs-1.0.1.tgz","fileCount":9,"integrity":"sha512-KYyLLRU6L3BGnBKMZBKYPyygCbcNmWYWBQ4yAwL8dTK4+l8wAUB7XovCb78Le70FlB5SiGNlJu9pH4FxXxlaDQ==","signatures":[{"sig":"MEUCIDnegAKHHmo9dCX7F/LZxqw8uo8rBct1Fx7DZzg6DALzAiEA/qiqwZlI/CAjHIgHP4JGQqteC9sYhINGCqzA8bwg0f4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":400447},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","gitHead":"d3d85f1645acb6101ac5ee2dad15f6c84b45b413","_npmUser":{"name":"jmussman","email":"jmussman@smallrock.net"},"repository":{"url":"git+https://github.com/jmussman/vitest-mock-commonjs.git","type":"git"},"_npmVersion":"10.5.2","description":"Module loader shims 'require' to allow mocking code-under-test CommonJS modules from vitest","directories":{},"_nodeVersion":"20.13.1","_hasShrinkwrap":false,"devDependencies":{"vitest":"latest"},"_npmOperationalInternal":{"tmp":"tmp/vitest-mock-commonjs_1.0.1_1724709408084_0.5391338286107481","host":"s3://npm-registry-packages"}},"1.0.2":{"name":"vitest-mock-commonjs","description":"Module loader shims 'require' to allow mocking code-under-test CommonJS modules from vitest","keywords":["vitest","CommonJS","require","mock"],"version":"1.0.2","homepage":"https://github.com/jmussman/vitest-mock-commonjs","author":{"name":"Joel Mussman","email":"jmussman@wsmallrock.net","url":"http://linkedin.com/in/jmussman"},"license":"MIT","type":"module","main":"./dist/index.js","repository":{"type":"git","url":"git+https://github.com/jmussman/vitest-mock-commonjs.git"},"devDependencies":{"vitest":"latest"},"_id":"vitest-mock-commonjs@1.0.2","gitHead":"928858dc82b768f1c61070c3db825b04041a7fca","types":"./dist/index.d.ts","bugs":{"url":"https://github.com/jmussman/vitest-mock-commonjs/issues"},"_nodeVersion":"20.13.1","_npmVersion":"10.5.2","dist":{"integrity":"sha512-vm/uIhhQRUqq8lxqDC8vAJZ1vOg/E6C/hxQM890niAG2OtF/N4IdYWFwtpR6ikxkIq76bLEaY6/Lej8SjeVPQw==","shasum":"4f6bc74db63df8e47c6f224d8bb30bf77b17edd2","tarball":"https://registry.npmjs.org/vitest-mock-commonjs/-/vitest-mock-commonjs-1.0.2.tgz","fileCount":9,"unpackedSize":400450,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAI6Wbz/XU67+ChdCSJSCcLz9FpvhFy3M8juTn6nVvQ4AiBWvbajEroBoFIKe26NOqn7cktXrhlSLGGg8sUMUaiPQg=="}]},"_npmUser":{"name":"jmussman","email":"jmussman@smallrock.net"},"directories":{},"maintainers":[{"name":"jmussman","email":"jmussman@smallrock.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/vitest-mock-commonjs_1.0.2_1724734562241_0.7288358972014819"},"_hasShrinkwrap":false}},"time":{"created":"2024-08-26T21:19:18.433Z","modified":"2024-08-27T04:56:02.684Z","1.0.0":"2024-08-26T21:19:18.853Z","1.0.1":"2024-08-26T21:56:48.270Z","1.0.2":"2024-08-27T04:56:02.450Z"},"bugs":{"url":"https://github.com/jmussman/vitest-mock-commonjs/issues"},"author":{"name":"Joel Mussman","email":"jmussman@wsmallrock.net","url":"http://linkedin.com/in/jmussman"},"license":"MIT","homepage":"https://github.com/jmussman/vitest-mock-commonjs","keywords":["vitest","CommonJS","require","mock"],"repository":{"type":"git","url":"git+https://github.com/jmussman/vitest-mock-commonjs.git"},"description":"Module loader shims 'require' to allow mocking code-under-test CommonJS modules from vitest","maintainers":[{"name":"jmussman","email":"jmussman@smallrock.net"}],"readme":"![Banner Light](./.assets/banner-vitest-mock-commonjs-light.png#gh-light-mode-only)\n![banner Dark](./.assets/banner-vitest-mock-commonjs-dark.png#gh-dark-mode-only)\n\n# vitest-mock-commonjs\n\n## Overview\n\nI started writing more vitest scenarios for Auth0 Action Scripts and Auth0 does not support ESM in Action Scripts, only CommonJS.\nSo I became frustrated trying to work around the limitation in vitest that it won't shim the Node.js \"require\" statement so that\nI can mock a CommonJS module.\nYou may be wondering about the name, but the goal is not to mock \"require\", it is to shim require so that we can mock CommonJS modules!\n\nI understand the problems facing the maintainers of vitest:\nthey are focused on ESM, and if you are using a package framework with its own module loader it may not be easy to hook into and shim\nto override the loading of CommonJS modules.\nAnd many folks may not consider it important to use both ESM and CommonJS at the same time.\nBut in the case of writing tests for Auth0 Action Scripts that is exactly what I want to do!\nThe scripts must be written in JavaScript with CommonJS, but I want the test suites to be written using ESM in either JavaScript\nor TypeScript!\n\nThe CommonJS module loader for Node.js is well defined and that is what I focused on first.\nWell, maybe not well defined in the documentation, but how it works is common knowledge!\nSo, to start I created a Node.js package that can be used to shim a \"mockForNodeRequire\" method onto the vi object in vitest\nto mock \"requires\" in the code under test.\nThis module is hidden behind a controller in index.js, which allows multiple shims for other module loaders\nto be injected into vitest using a common framework.\n\n## Configuration and Use\n\n### Supported module loaders\n\n| Framework Name | vi.{Method} or Function Name |\n|---|---|\n| Node.js | mockForNodeRequire |\n\n### Installation\n\nWith npm or yarn import this module into the test module where you want to mock CommonJS modules.\nSince this is intended for unit testing, import the module as a development dependency.\nThe module overrrides the module loader and allows the mocking of multiple CommonJS modules.\n\n```\n$ npm install --save-dev vitest-mockrequire\n```\n\nThis module depends on the \"latest\" version of vitest; the test suite also needs to do that to make\nsure it is using the same version or the injected methods will not be found.\nThis happens because npm and yarn support managing multiple module versions adjacent to each other\nin node_modules.\n\nThe package.json over the test-suite should specify \"latest\" as the version for vitest:\n\n```\n{\n    \"devDependencies\": {\n        \"vitest-mock-commonjs\": \"latest\"\n        \"vitest\": \"latest\"\n    }\n}\n```\n\n### Inclusion in the test suite\n\nImport vitest and this module in the test suite.\nThe order of import is not important, vitest-mockrequire will import vi itself to inject the methods.\nWhat other exports from vitest you import are up to you, this is just an example:\n\n```\nimport { beforeAll, beforeEach, vi } from 'vitest'\nimport 'vitest-mock-commonjs'\n```\n\nvitest-mockrequire also exports the mock creation functions for direct use, simply import the named functions:\n\n```\nimport { mockForNodeRequire } from 'vitest-mock-commonjs'\n```\n\n### Mocking a CommonJS module\n\nMock CommonJS modules by calling the mock creation function.\nThis example shows both forms.\n{} is used as a placeholder here for the actual test double:\n\n```\nvi.mockForNodeRequire('auth0', {}) // or directly with mockForNodeRequire('auth0', {})\n```\n\nTypeScript declarations are included in the index.d.ts file, so this should be directly useable\nwith TypeScript.\n\n### The test double (a side-trip on \"how to mock a CommonJS module\")\n\nThe test double depends on the what the CommonJS module does.\nThe auth0 module is a good multi-level example.\nThe module itself does not have a default export.\nThe ManagementClient property is a class that produces an object with a users property which\nis all we are interested in at the moment.\nThe test double must be in global space so that the mocked methods can be referenced in the tests.\nIt is probably best to \"hoist\" them too, in case there is anything else in a common declaration\nused for mocking or spying:\n\n```\nconst mocks = vi.hoisted(() => {\n\n    const managementClient = {\n\n        users: {\n\n            delete: vi.fn(async (requestParameters) => new Promise((resolve) => resolve()))\n        }\n    }\n\n    class ManagementClient {\n\n        constructor() {\n\n            this.users =  managementClient.users\n        }\n    }\n\n    const mocks = {\n\n        auth0Mock: {\n            \n            ManagementClient: ManagementClient,\n            managementClient: managementClient\n        }\n    }\n\n    return mocks\n})\n```\n\nIf you follow this carefully, the class definition and the static object the class is always evaluated as (for mocking) are\nattached to a property hoisted before using vitest and available in the global \"mocks\" variable.\nThat way they can both be referenced for \"expect\" assertions in the tests!\n\nWe only need to mock the CommonJS module(s) once.\nThere is no requirement to hoist this call, so that could be done in the hoisted code or more likely it could be placed in\na definition of *beforeAll*:\n\n```\ndescribe('Action tests', async () => {\n\n    beforeAll(async () => {\n\n        vi.mockForNodeRequire('auth0', mocks.auth0Mock)\n    })\n```\n\nAll that is left is checking to see if the function was actually called in the code under test:\n\n```\n    it('Rejects authentication when the only deny entry is the denied user', async () => {\n\n        mocks.eventMock.secrets.deny = 'calicojack@pyrates.live'\n        mocks.eventMock.user.email = 'calicojack@pyrates.live'\n\n        await onExecutePostLogin(mocks.eventMock, mocks.apiMock)\n\n        expect(mocks.auth0Mock.managementClient.users.delete).toHaveBeenCalled()\n    })\n```\n\nThere are test double definitions missing in the example, hinted at by the individual test case above.\nThe whole working, stand-alone example that uses vitest-mockrequire and from which\nthese examples were pulled can be seen at https://github.com/jmussman/auth0-block-idp-signup. \n\n### The code-under-test (CUT)\n\nThe mocked CommonJS module will be loaded in the code under test because the module loader has\nbeen overridden to do so.\nThis is what the CUT from the example looks like, and there is nothing in there to knowingly support\nthe test (a fundamental principles of testing):\n\n```\nconst ManagementClient = require('auth0').ManagementClient;\n\nconst managementClient = new ManagementClient({\n\n    domain: event.secrets.domain,\n    clientId: event.secrets.clientId,\n    clientSecret: event.secrets.clientSecret,\n});\n\n...\n\nawait managementClient.users.delete({ id: event.user.user_id });\n```\n\n## Implementation (how it works)\n\nvitest fits with ESM very well, while jest fits with CommonJS.\nBecause the focus is ESM, vitest-mockrequire is only delivered as an ESM module.\nOf course an index.d.ts file is provided for TypeScript compatibility.\n\n### \n\nThis model defines a single function that is used to mock CommonJS modules but\nwill also provide the shim for those test doubles to work:\n\n```\nasync function mockForNodeRequire(module, testDouble) {\n}\n\nvi.mockForNodeRequire = mockForNodeRequire\n\nexport default mockForNodeRequire\n```\n\n### Multiple Module Mocking\n\nThis model extends some examples that are found around the Internet, but adds mocking multiple\nCommonJS modules at the same time.\nAn object named *testDoubles* is attached to the function object to remember the test double\nfor each module, using the module name as an object property name.\nStrict mode blocks a standalone function from having a *this* pointer that points to itself;\nto get arround this the function is named using the classic JavaScript declaration and the name references\nthe function within itself:\n\n```\nasync function  mockForNodeRequire(module, testDouble) {\n\n    if (!Object.getOwnPropertyNames(mockRequire).testDoubles) {\n\n        mockForNodeRequire.testDoubles = {}\n```\n\n### Overridding Module._load\n\nOverriding the Node.js module loader is fairly simple with ESM.\nIn this model the override is handled in the function that will be used for mocking, the first time the\nfunction is called.\nTo override Node.js \"module\" is imported and the _load function overridden.\nAfter the override, or if it was already overridden, the test double is added to the testDoubles object.\nWhen require is called in the CUT, the new _load method looks at the module requested and either serves\nthe double or uses the original _load method to serve the actual module if no double exists:\n\n```\nasync function  mockForNodeRequire(module, testDouble) {\n\n    if (!mockForNodeRequire.testDoubles) {\n\n        mockForNodeRequire.testDoubles = {}\n\n        const { Module } = await import('module')\n\n        Module._load_original = Module._load\n\n        Module._load = (uri, parent) => {\n\n            const result = mocNodeRequire.testDoubles[uri] ?? Module._load_original(uri, parent)\n\n            return result\n        }\n    }\n\n    if (module && testDouble) {\n\n        mockForNodeRequire.testDoubles[module] = testDouble\n    }\n}\n\nmockForNodeRequire()\n\nexport default mockForNodeRequire\n```\n\nThere is a conditional check provided to avoid trying to record a double when no module is specified.\nThe shim does need to be hoisted, but if the function is called without a module argument during\ninitialization the the shim is hoisted anyways because module initialization is hoisted.\nYou may wonder why do that at all, why not just define the shim outside of the function?\nWe keep it as a lambda inside so that it has closure access to the testDouble property!\n\nThe last step is the index.js module.\nThis loads all of the defined mock creation modules for different module loaders and attaches them\ntoo the vi object:\n\n```\nimport { vi } from 'vitest'\nimport mockForNodeRequire from '../src/mockForNodeRequire'\n\nvi.mockForNodeRequire = mockForNodeRequire\n\nexport { mockForNodeRequire }\n```\n\n## License\n\nThe code is licensed under the MIT license. You may use and modify all or part of it as you choose, as long as attribution to the source is provided per the license. See the details in the [license file](./LICENSE.md) or at the [Open Source Initiative](https://opensource.org/licenses/MIT).\n\n\n<hr>\nCopyright © 2024 Joel A Mussman. All rights reserved.","readmeFilename":"README.md"}