-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathproxyquire.js
More file actions
164 lines (136 loc) · 4.97 KB
/
proxyquire.js
File metadata and controls
164 lines (136 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import Module from 'module';
import resolve from 'resolve';
import forEach from 'lodash.foreach';
import * as aliases from './aliases';
const path = require('path');
function getRequest(request, parentPath) {
const parentDir = parentPath;
if (aliases.isAliased(request)) {
return aliases.relative(parentDir, request);
} else {
return request;
}
}
// The moduleId is the absolute path of the module on your system.
function getModuleId(request, parentPath) {
return resolve.sync(request, {
basedir: path.dirname(parentPath),
extensions: Object.keys(require.extensions),
});
}
function makeMockModule(moduleId, exports) {
const mockedModule = new Module(moduleId);
mockedModule.exports = exports;
return mockedModule;
}
function getFromCache(moduleId) {
return Module._cache[moduleId];
}
function removeFromCache(moduleId) {
delete require.cache[moduleId];
delete Module._cache[moduleId];
}
function addToCache(moduleId, module) {
Module._cache[moduleId] = module;
require.cache[moduleId] = module;
}
function replaceCacheEntry(moduleId, module) {
if (module) {
removeFromCache(moduleId);
addToCache(moduleId, module);
}
}
function warmUpModuleCache(request, parent) {
const moduleId = getModuleId(request, parent.filename);
// Load the module without stubs so that Module._cache is properly
// warmed up and so that a dependency of our thing under test is not
// instantiated with a stub instead of the real thing.
Module._load(request, parent);
// the module should not be a child to this module.
parent.children.pop();
// We're only doing this because we're nice and we clean up after ourselves
const module = getFromCache(moduleId);
// Remove the module from the cache (because we want to load it with stubs after this)
removeFromCache(moduleId);
return module;
}
// delete this module from the cache to force re-require in order to allow resolving test module via parent.module
removeFromCache(require.resolve(__filename));
// delete this module from the module.parent children to prevent leaks
module.parent.children.splice(
module.parent.children.indexOf(module),
1,
);
/**
* proxyquire - require a module with a list of mocks instead of their real implementations
*
* type Request = string;
* type ModuleExports = {
* [methodOrProperty: string]: any;
* default?: any;
* }
* type Stubs = {
* [modulePathOrName: string]: ModuleExports;
* }
*
* @param {Request} request - a module name / relative path (e.g. 'moment' or '../StockPileStore')
* @param {Stubs} stubs - a key value pair of modules to mock and the mock implementations
* @returns {exports}
*/
export default function proxyquire(req, stubs) {
let error;
let moduleLoadedWithStubs;
const parent = module.parent; // fancy node.js thing that means 'the module which required *this file*'
const request = getRequest(req, parent.filename);
const requestId = getModuleId(request, parent.filename);
// We store the "real" modules in here so that we can clean up after
// ourselves after we have loaded the module.
//
// type ModuleCache {
// [absolutePath: string]: Module
// }
const tempCache = new Map();
// We load the module once without stubs so that the caches are all
// properly loaded. We hold on to the old value so we can clean up after
// ourselves and put it back in the cache once we're done.
const trueModule = warmUpModuleCache(request, parent);
try {
// We replace the real modules from the Module cache by our stubs.
forEach(stubs, (stub, stubPath) => {
const stubRequest = getRequest(stubPath, requestId);
const moduleId = getModuleId(stubRequest, requestId);
tempCache.set(moduleId, getFromCache(moduleId));
replaceCacheEntry(moduleId, makeMockModule(moduleId, stub));
});
moduleLoadedWithStubs = Module._load(request, parent);
// the mocked module should not be a child of this module.
parent.children.pop();
} catch (e) {
// We actually want to show that error, but we also want to clean up
// after ourselves before we do anything of the sort. Otherwise we'd be
// leaving the mocks in the module cache... and that's pretty bad!
error = e;
}
// We clean up after ourselves by putting back the true module values
// into the cache
forEach(stubs, (stub, stubPath) => {
const stubRequest = getRequest(stubPath, requestId);
const moduleId = getModuleId(stubRequest, requestId);
replaceCacheEntry(moduleId, tempCache.get(moduleId));
tempCache.delete(moduleId); // because I'm paranoid
});
// We put back the module value into the cache
replaceCacheEntry(requestId, trueModule);
if (error) {
// finally throw that nasty error!
throw error;
}
return moduleLoadedWithStubs;
}
// temporary, while w
export const noCallThru = () => proxyquire;
// exported for testing against memory leaks
export const __parent__ = module.parent;
export const alias = aliases.add;
export const unalias = aliases.remove;
export const getAliases = aliases.get;