Skip to content

Commit 1257541

Browse files
committed
refactor(mocking-modules): rename the project
1 parent 0ef9cb5 commit 1257541

33 files changed

+134
-130
lines changed

.changeset/loud-zebras-joke.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@web/test-runner-intercept': patch
2+
'@web/test-runner-module-mocking': patch
33
---
44

5-
initial commit of @web/test-runner-intercept
5+
initial commit of @web/test-runner-module-mocking

package-lock.json

Lines changed: 25 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"start:build": "node packages/dev-server/dist/bin.js --root-dir _site --open",
3434
"test": "npm run test:node && npm run test:browser && node scripts/workspaces-scripts-bin.mjs test:ci",
3535
"test:browser": "npm run test:browser --workspaces --if-present",
36-
"test:node": "mocha \"packages/!(*test-runner-selenium|*test-runner-webdriver|*test-runner-intercept)/test/**/*.test.{ts,js,mjs,cjs}\" && TS_NODE_PROJECT='./tsconfig.node-16-base.json' mocha \"packages/test-runner-intercept/test/**/*.test.{ts,js,mjs,cjs}\" --loader=ts-node/esm",
36+
"test:node": "mocha \"packages/!(*test-runner-selenium|*test-runner-webdriver|*test-runner-module-mocking)/test/**/*.test.{ts,js,mjs,cjs}\" && TS_NODE_PROJECT='./tsconfig.node-16-base.json' mocha \"packages/test-runner-module-mocking/test/**/*.test.{ts,js,mjs,cjs}\" --loader=ts-node/esm",
3737
"types": "wireit",
3838
"update": "npm run update:mjs-dts-entrypoints && npm run update:tsconfigs",
3939
"update-dependency": "node scripts/update-dependency.js",

packages/test-runner-intercept/browser/index.d.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/test-runner-intercept/browser/index.js

Lines changed: 0 additions & 26 deletions
This file was deleted.

packages/test-runner-intercept/test/fixtures/inexistent/browser-test.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/test-runner-intercept/test/fixtures/relative/browser-test.js

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
# Web Test Runner Intercept
1+
# Web Test Runner Module Mocking
22

3-
Web test runner plugin that allows the interception of modules during test runs. Often, while creating tests, there is the need to change the behavior of a dependency of the system under test. This is needed to cover all branches of the system under test, for performance reasons or sometimes because the dependency cannot operate correctly in the test environment. Dependencies that are on the global window object are easy to change with a mock or a stub. ES Modules, however, and specifically their exports cannot be altered. This plugin introduces a hook that enables the interception of modules and allows altering its exports.
3+
Web Test Runner package that enables mocking of ES Modules during test runs.
44

55
## Concept
66

7-
Next to a web test runner plugin that enables module interception, this package also exposes a function `interceptModule()` to be used in the test run. This function allows to define what module needs to be intercepted and returns an object with all the exports of the module as its properties. Changing these properties allows to rewire the intercepted module without changing the system under test.
7+
A typical step when authoring tests is to change the behavior of dependencies of a system under test. This is usually needed to cover all branches of the system under test, for performance reasons or sometimes because a dependency cannot operate correctly in the test environment. Test authors will use mocks, stubs or spies to change or monitor the original implementation.
8+
9+
Dependencies that are on the global window object are easy to change. ES Modules, however, and specifically their exports bindings cannot be reassigned. This package exposes a Web Test Runner plugin that can intercept module imports on the server. When a module is intercepted, the plugins injects extra code that allows reassigning its exports.
10+
11+
Once the plugin is active in the Web Test Runner config, a test author can use the `importMockable()` function to start rewiring modules in test runs. The function imports the intercepted module and returns a mockable object. This objects contains all the exports of the module as its properties. Reassigning these properties allows rewiring the intercepted module, and the system under test will use the updated implementation.
812

913
```js
10-
import { interceptModule } from '@web/test-runner-intercept';
14+
import { importMockable } from '@web/test-runner-module-mocking';
1115

12-
const externalLibrary = await interceptModule('external-library');
16+
const externalLibrary = await importMockable('external-library');
1317

1418
// Return the original function that 'external-library' exposed in the `externalFunction` named export
1519
externalLibrary.externalFunction; // f externalFunction() { ... }
@@ -21,51 +25,13 @@ const { externalFunction } = await import('external-library');
2125
externalFunction; // () => console.log('hello world')
2226
```
2327

24-
### Caveats
25-
26-
#### Order of imports
27-
28-
In order to intercept a module, the module should not be referenced yet. (Once a module is loaded, the module loader also loads all its dependent modules) This means the module containing the system under test should be loaded _after_ the module interception. As interception is achieved using a function, this also means the system under test should _always_ be loaded using a dynamic import in order to be done after the interception.
29-
30-
```js
31-
import { interceptModule } from '@web/test-runner-intercept';
32-
33-
// First, intercept
34-
const externalLibrary = await interceptModule('external-library');
35-
36-
// Second, load the system under test.
37-
const systemUnderTest = await import('../../src/system-under-test.js');
38-
39-
// Run tests
40-
...
41-
```
42-
43-
#### Types of module specifiers
44-
45-
Currently, two types of module specifiers are supported: bare modules and server relative modules.
46-
47-
```javascript
48-
// bare module, located in `node_modules`
49-
await interceptModule('external-library');
50-
51-
// server relative module
52-
await interceptModule('/src/library-to-intercept.js');
53-
```
54-
55-
In order to use regular relative references, `import.meta.resolve()` and `new URL().pathname` can be used.
56-
57-
```javascript
58-
// relative module
59-
await interceptModule(new URL(import.meta.resolve('../src/library-to-intercept.js')).pathname);
60-
```
61-
6228
## Usage
6329

6430
### Setup
6531

6632
```js
6733
// web-test-runner.config.mjs
68-
import { interceptModulePlugin } from '@web/test-runner-intercept/plugin.js';
34+
import { interceptModulePlugin } from '@web/test-runner-module-mocking/plugin.js';
6935

7036
export default {
7137
plugins: [interceptModulePlugin()],
@@ -89,9 +55,9 @@ export function getTimeOfDay() {
8955

9056
```js
9157
// test/getTimeOfDay.test.js
92-
import { interceptModule } from '@web/test-runner-intercept';
58+
import { importMockable } from '@web/test-runner-module-mocking';
9359

94-
const timeLibrary = await interceptModule('time-library');
60+
const timeLibrary = await importMockable('time-library');
9561
const { getTimeOfDay } = await import('../src/getTimeOfDay.js');
9662

9763
describe('getTimeOfDay', () => {
@@ -107,15 +73,15 @@ describe('getTimeOfDay', () => {
10773

10874
### Extended test scenario
10975

110-
This scenario showcases how to use `@web/test-runner-intercept` together with `chai` and `sinon`.
76+
This scenario showcases how to use `@web/test-runner-module-mocking` together with `chai` and `sinon`.
11177

11278
```js
11379
// test/getTimeOfDay.test.js
11480
import { stub } from 'sinon';
11581
import { expect } from 'chai';
116-
import { interceptModule } from '@web/test-runner-intercept';
82+
import { importMockable } from '@web/test-runner-module-mocking';
11783

118-
const timeLibrary = await interceptModule('time-library');
84+
const timeLibrary = await importMockable('time-library');
11985
const { getTimeOfDay } = await import('../src/getTimeOfDay.js');
12086

12187
describe('getTimeOfDay', () => {
@@ -139,3 +105,41 @@ describe('getTimeOfDay', () => {
139105
});
140106
});
141107
```
108+
109+
## Caveats
110+
111+
### Order of imports
112+
113+
In order to intercept a module, the module should not be referenced yet. (Once a module is loaded, the module loader also loads all its dependent modules) This means the module containing the system under test should be loaded _after_ the module interception. As interception is achieved using a function, this also means the system under test should _always_ be loaded using a dynamic import in order to be done after the interception.
114+
115+
```js
116+
import { importMockable } from '@web/test-runner-module-mocking';
117+
118+
// First, intercept
119+
const externalLibrary = await importMockable('external-library');
120+
121+
// Second, load the system under test.
122+
const systemUnderTest = await import('../../src/system-under-test.js');
123+
124+
// Run tests
125+
...
126+
```
127+
128+
### Types of module specifiers
129+
130+
Currently, two types of module specifiers are supported: bare modules and server relative modules.
131+
132+
```javascript
133+
// bare module, located in `node_modules`
134+
await importMockable('external-library');
135+
136+
// server relative module
137+
await importMockable('/src/library-to-intercept.js');
138+
```
139+
140+
In order to use regular relative references, `import.meta.resolve()` and `new URL().pathname` can be used.
141+
142+
```javascript
143+
// relative module
144+
await importMockable(new URL(import.meta.resolve('../src/library-to-intercept.js')).pathname);
145+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Import a mockable version of a module to change its implementation.
3+
* @param {string} moduleSpecifier The specifier of the module. Bare module specifiers and server relative specifiers are supported. Regular relative paths are not supported
4+
* @returns {Promise<Record<string, func> | void >} Object with reassignable properties that match every export of the specified module.
5+
* If the specified module contains a default export, the export will be available from the `default` property. Only function expressions are supported when rewiring default exports.
6+
*/
7+
export function importMockable(moduleSpecifier: string): Promise<Record<string, func> | void>;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Import a mockable version of a module to change its implementation.
3+
* @param {string} moduleSpecifier The specifier of the module. Bare module specifiers and server relative specifiers are supported. Regular relative paths are not supported
4+
* @returns {Promise<Record<string, func> | void >} Object with reassignable properties that match every export of the specified module.
5+
* If the specified module contains a default export, the export will be available from the `default` property. Only function expressions are supported when rewiring default exports.
6+
*/
7+
export async function importMockable(moduleSpecifier) {
8+
if (moduleSpecifier.includes('./')) {
9+
throw new Error(
10+
`Parameter \`moduleName\` ('${moduleSpecifier}') contains a relative reference. This is not supported. Convert \`moduleName\` first to a server relative path. (eg. \`new URL(import.meta.resolve(moduleName)).pathname\`)`,
11+
);
12+
}
13+
14+
let module;
15+
try {
16+
module = await import(`/__intercept-module__?${moduleSpecifier}`);
17+
} catch (e) {
18+
throw new Error(
19+
`Module interception is not active. Make sure the \`interceptModulePlugin\` of \`@web/test-runner-module-mocking\` is added to the Test Runner config.`,
20+
);
21+
}
22+
if (module.__wtr_error__) {
23+
throw new Error(module.__wtr_error__);
24+
}
25+
return module.__wtr_intercepted_module__;
26+
}

packages/test-runner-intercept/package.json renamed to packages/test-runner-module-mocking/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
{
2-
"name": "@web/test-runner-intercept",
2+
"name": "@web/test-runner-module-mocking",
33
"version": "0.1.0",
44
"publishConfig": {
55
"access": "public"
66
},
7-
"description": "Plugin for intercepting modules in @web/test-runner",
7+
"description": "Package to enable mocking modules in @web/test-runner",
88
"license": "MIT",
99
"repository": {
1010
"type": "git",
1111
"url": "https://github.com/modernweb-dev/web.git",
12-
"directory": "packages/test-runner-intercept"
12+
"directory": "packages/test-runner-module-mocking"
1313
},
1414
"author": "modern-web",
15-
"homepage": "https://github.com/modernweb-dev/web/tree/master/packages/test-runner-intercept",
15+
"homepage": "https://github.com/modernweb-dev/web/tree/master/packages/test-runner-module-mocking",
1616
"main": "browser/index.js",
1717
"type": "module",
1818
"exports": {
@@ -48,8 +48,10 @@
4848
"runner",
4949
"testrunner",
5050
"module",
51-
"interception",
52-
"intercept"
51+
"intercept",
52+
"mock",
53+
"stub",
54+
"spy"
5355
],
5456
"dependencies": {
5557
"@web/dev-server-core": "^0.7.0",

packages/test-runner-intercept/test/fixtures/bare/browser-test.js renamed to packages/test-runner-module-mocking/test/fixtures/bare/browser-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from '../chai.js';
2-
import { interceptModule } from '../../../browser/index.js';
2+
import { importMockable } from '../../../browser/index.js';
33

4-
const timeLibrary = await interceptModule('time-library/hour');
4+
const timeLibrary = await importMockable('time-library/hour');
55
const { getTimeOfDay } = await import('./fixture/getTimeOfDay.js');
66

77
let backup;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { importMockable } from '../../../browser/index.js';
2+
3+
await importMockable('/inexistent-module.js');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { importMockable } from '../../../browser/index.js';
2+
3+
await importMockable('./file.js');

packages/test-runner-intercept/test/fixtures/server-relative/browser-test.js renamed to packages/test-runner-module-mocking/test/fixtures/server-relative/browser-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { expect } from '../chai.js';
2-
import { interceptModule } from '../../../browser/index.js';
2+
import { importMockable } from '../../../browser/index.js';
33

44
const path = new URL(import.meta.resolve('./fixture/time-library.js')).pathname;
5-
const timeLibrary = await interceptModule(path);
5+
const timeLibrary = await importMockable(path);
66
const { getTimeOfDay } = await import('./fixture/getTimeOfDay.js');
77

88
let backup;

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"path": "./packages/test-runner-browserstack/tsconfig.json"
102102
},
103103
{
104-
"path": "./packages/test-runner-intercept/tsconfig.json"
104+
"path": "./packages/test-runner-module-mocking/tsconfig.json"
105105
},
106106
{
107107
"path": "./packages/test-runner-junit-reporter/tsconfig.json"

0 commit comments

Comments
 (0)