@dannysir/js-te 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -33
- package/babelCollectMocks.js +34 -0
- package/babelTransformImport.js +280 -177
- package/bin/cli.js +8 -6
- package/bin/utils/transformFiles.js +2 -2
- package/package.json +4 -3
- package/src/mock/collectMocks.js +28 -0
package/README.md
CHANGED
|
@@ -3,23 +3,19 @@
|
|
|
3
3
|
Jest에서 영감을 받아 만든 가벼운 JavaScript 테스트 프레임워크입니다.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
## [📎 최근 업데이트 0.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### 문서 수정
|
|
10
|
-
- README.md 내에 type 설정 관련 설명 수정
|
|
11
|
-
- 0.2.1 버전부터 ESM 방식과 Common JS 방식 모두 허용
|
|
12
|
-
- 개발 블로그 링크 추가
|
|
13
|
-
|
|
14
|
-
### 설정 수정
|
|
15
|
-
- package.json
|
|
16
|
-
- 레포지토리 및 이슈 링크 추가
|
|
17
|
-
|
|
18
|
-
### `rollup.config.js` output 파일명 수정
|
|
19
|
-
- 기존 `.cjs.js`와 같은 이름에서 `.cjs`로 수정
|
|
20
|
-
- `esm.js`에서 `.mjs`로 수정
|
|
6
|
+
## [📎 최근 업데이트 0.3.0v](https://github.com/dannysir/js-te-package/blob/main/CHANGELOG.md)
|
|
21
7
|
|
|
22
8
|
|
|
9
|
+
### `mock` 이후 import를 해야하는 문제 해결
|
|
10
|
+
- 문제 : 기존의 경우 모킹 기능 이용시 반드시 동적 import문을 mock 다음에 작성해야 했음
|
|
11
|
+
- 해결
|
|
12
|
+
- 기존 `mockStore`를 직접 비교하여 import하는 방식에서 wrapper 패턴을 이용하도록 적용
|
|
13
|
+
- 모듈을 새로운 함수로 만들어 함수를 실행할 때마다 `mockStore`와 비교하여 값을 리턴하도록 수정
|
|
14
|
+
### 모듈 변환 최적화
|
|
15
|
+
- 문제 : 앞선 변경으로 인해 모든 파일의 모듈들이 사용될 때마다 `mockStore`와 비교하는 로직이 실행됨
|
|
16
|
+
- 해결
|
|
17
|
+
- `cli`로직에 mock을 미리 검사하여 mock 경로를 미리 저장하는 로직을 추가
|
|
18
|
+
- 미리 확인한 mock 경로를 이용해 import문이 만약 저장된 경로일 때만 babel 변환
|
|
23
19
|
|
|
24
20
|
---
|
|
25
21
|
## 설치
|
|
@@ -155,10 +151,23 @@ Babel을 사용해서 import/require 구문을 변환하여 mock 함수를 가
|
|
|
155
151
|
1. `mock(path, mockObj)` 선언 확인
|
|
156
152
|
2. `path`를 key로 이용해 Map에 저장
|
|
157
153
|
2. Babel로 코드 변환
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
```jsx
|
|
155
|
+
// 0.3.0 버전 이후
|
|
156
|
+
const _original = await import('./random.js');
|
|
157
|
+
const random = (...args) => {
|
|
158
|
+
const module = mockStore.has('/path/to/random.js')
|
|
159
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
160
|
+
: _original;
|
|
161
|
+
return module.random(...args);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// 0.3.0 버전 이전
|
|
165
|
+
const _original = await import('./random.js');
|
|
166
|
+
const _module = mockStore.has('/path/to/random.js')
|
|
167
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
168
|
+
: _original;
|
|
169
|
+
const {random1, random2} = _module;
|
|
170
|
+
```
|
|
162
171
|
3. 테스트 실행
|
|
163
172
|
4. 원본 파일 복구
|
|
164
173
|
|
|
@@ -170,8 +179,10 @@ Babel을 사용해서 import/require 구문을 변환하여 mock 함수를 가
|
|
|
170
179
|
|
|
171
180
|
1. 반드시 경로는 절대 경로로 입력해주세요.
|
|
172
181
|
- babel이 import문에서 절대 경로로 변환하여 확인을 하기 때문에 반드시 절대 경로로 등록해주세요.
|
|
173
|
-
2. import문을 반드시 mocking 이후에
|
|
174
|
-
- mocking 전에 import를 하게 되면 mocking되기 전의 모듈을 가져오게
|
|
182
|
+
2. ~~import문을 반드시 mocking 이후에 선언해주세요.~~
|
|
183
|
+
- ~~mocking 전에 import를 하게 되면 mocking되기 전의 모듈을 가져오게 됩니다.~~
|
|
184
|
+
|
|
185
|
+
> **0.3.0 버전부터 import문을 mock선언 이후에 하지 않아도 됩니다.**
|
|
175
186
|
|
|
176
187
|
**💡 부분 모킹(Partial Mocking)**
|
|
177
188
|
|
|
@@ -184,14 +195,13 @@ export const subtract = (a, b) => a - b;
|
|
|
184
195
|
export const multiply = (a, b) => a * b;
|
|
185
196
|
|
|
186
197
|
// math.test.js
|
|
198
|
+
const { add, multiply } = import('./math.js'); // 0.3.0 버전부터는 최상단에 선언 가능
|
|
199
|
+
|
|
187
200
|
test('부분 모킹 예제', async () => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
multiply: () => 100 // multiply만 모킹
|
|
201
|
+
mock('/Users/san/untitled/index.js', {
|
|
202
|
+
multiply: () => 100
|
|
191
203
|
});
|
|
192
204
|
|
|
193
|
-
const { add, subtract, multiply } = await import('./math.js');
|
|
194
|
-
|
|
195
205
|
expect(add(2, 3)).toBe(5); // 원본 함수 사용
|
|
196
206
|
expect(subtract(5, 3)).toBe(2); // 원본 함수 사용
|
|
197
207
|
expect(multiply(2, 3)).toBe(100); // 모킹된 함수 사용
|
|
@@ -222,17 +232,15 @@ import { random } from './random.js'; // 자유롭게 import하면 babel에서
|
|
|
222
232
|
export const play = () => random() * 10;
|
|
223
233
|
|
|
224
234
|
// game.test.js
|
|
235
|
+
import {play} from './game.js';
|
|
236
|
+
|
|
225
237
|
test('랜덤 함수 모킹', async () => {
|
|
226
238
|
// 1. 먼저 모킹
|
|
227
239
|
mock('/Users/san/Js-Te/test-helper/random.js', { // 반드시 절대 경로로 등록
|
|
228
240
|
random: () => 0.5
|
|
229
241
|
});
|
|
230
242
|
|
|
231
|
-
// 2.
|
|
232
|
-
const { play } = await import('./game.js');
|
|
233
|
-
// 또는: const { play } = require('./game.js');
|
|
234
|
-
|
|
235
|
-
// 3. 모킹된 값 사용
|
|
243
|
+
// 2. 모킹된 값 사용
|
|
236
244
|
expect(play()).toBe(5);
|
|
237
245
|
});
|
|
238
246
|
```
|
|
@@ -365,11 +373,14 @@ describe('문자열 테스트', () => {
|
|
|
365
373
|
#### 전체 모킹
|
|
366
374
|
```javascript
|
|
367
375
|
// mocking.test.js
|
|
376
|
+
import {random} from '../src/test-helper/game.js'; // 0.2.4 버전부터 import문 상단 배치 가능
|
|
377
|
+
|
|
368
378
|
test('[mocking] - mocking random function', async () => {
|
|
369
379
|
mock('/Users/san/Js-Te/test-helper/random.js', {
|
|
370
380
|
random: () => 3,
|
|
371
381
|
});
|
|
372
|
-
|
|
382
|
+
// 0.3.0 버전 이전까지는 반드시 mock 이후 동적 import문 작성
|
|
383
|
+
// const {play} = await import('../src/test-helper/game.js');
|
|
373
384
|
expect(play()).toBe(30);
|
|
374
385
|
});
|
|
375
386
|
|
|
@@ -413,7 +424,7 @@ test('[partial mocking] - mock only multiply', async () => {
|
|
|
413
424
|
|
|
414
425
|
## 만든 이유
|
|
415
426
|
|
|
416
|
-
|
|
427
|
+
Jest를 사용하며 JavaScript 테스트 라이브러리의 구조가 궁금하여 만들게 되었습니다.
|
|
417
428
|
|
|
418
429
|
## 라이선스
|
|
419
430
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { BABEL } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
export const createMockCollectorPlugin = (mockedPaths) => {
|
|
5
|
+
return ({types: t}) => {
|
|
6
|
+
return {
|
|
7
|
+
visitor: {
|
|
8
|
+
CallExpression(nodePath, state) {
|
|
9
|
+
if (!t.isIdentifier(nodePath.node.callee, {name: 'mock'})) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const args = nodePath.node.arguments;
|
|
14
|
+
if (args.length < 1 || !t.isStringLiteral(args[0])) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const mockPath = args[0].value;
|
|
19
|
+
const currentFilePath = state.filename || process.cwd();
|
|
20
|
+
const currentDir = path.dirname(currentFilePath);
|
|
21
|
+
|
|
22
|
+
let absolutePath;
|
|
23
|
+
if (mockPath.startsWith(BABEL.PERIOD)) {
|
|
24
|
+
absolutePath = path.resolve(currentDir, mockPath);
|
|
25
|
+
} else {
|
|
26
|
+
absolutePath = mockPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
mockedPaths.add(absolutePath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
package/babelTransformImport.js
CHANGED
|
@@ -1,214 +1,317 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import {BABEL, MOCK} from "./constants.js";
|
|
3
3
|
|
|
4
|
-
export const babelTransformImport = (
|
|
5
|
-
return {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
t.
|
|
11
|
-
|
|
12
|
-
t.
|
|
13
|
-
|
|
4
|
+
export const babelTransformImport = (mockedPaths = null) => {
|
|
5
|
+
return ({types: t}) => {
|
|
6
|
+
return {
|
|
7
|
+
visitor: {
|
|
8
|
+
Program(path) {
|
|
9
|
+
const mockStoreDeclaration = t.VariableDeclaration('const', [
|
|
10
|
+
t.VariableDeclarator(
|
|
11
|
+
t.Identifier(MOCK.STORE_NAME),
|
|
12
|
+
t.MemberExpression(
|
|
13
|
+
t.Identifier('global'),
|
|
14
|
+
t.Identifier(MOCK.STORE_NAME)
|
|
15
|
+
)
|
|
14
16
|
)
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
17
|
+
]);
|
|
18
|
+
path.node.body.unshift(mockStoreDeclaration);
|
|
19
|
+
},
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
ImportDeclaration(nodePath, state) {
|
|
22
|
+
const source = nodePath.node.source.value;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if (source === MOCK.STORE_PATH) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const currentFilePath = state.filename || process.cwd();
|
|
29
|
+
const currentDir = path.dirname(currentFilePath);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
let absolutePath;
|
|
32
|
+
if (source.startsWith(BABEL.PERIOD)) {
|
|
33
|
+
absolutePath = path.resolve(currentDir, source);
|
|
34
|
+
} else {
|
|
35
|
+
absolutePath = source;
|
|
36
|
+
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
if (mockedPaths && !mockedPaths.has(absolutePath)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const specifiers = nodePath.node.specifiers;
|
|
43
|
+
const originalVarName = nodePath.scope.generateUidIdentifier('original');
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
const _original = await import('./random.js');
|
|
44
|
-
const _module = mockStore.has('/path/to/random.js')
|
|
45
|
-
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
46
|
-
: _original;
|
|
47
|
-
const {random1, random2} = _module;
|
|
48
|
-
*/
|
|
45
|
+
/*
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
const _original = await import('./random.js');
|
|
48
|
+
const random = (...args) => {
|
|
49
|
+
const module = mockStore.has('/path/to/random.js')
|
|
50
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
51
|
+
: _original;
|
|
52
|
+
return module.random(...args);
|
|
53
|
+
};
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const originalDeclaration = t.variableDeclaration(BABEL.CONST, [
|
|
57
|
+
t.variableDeclarator(
|
|
58
|
+
originalVarName,
|
|
59
|
+
t.awaitExpression(
|
|
60
|
+
t.importExpression(t.stringLiteral(source))
|
|
61
|
+
)
|
|
55
62
|
)
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
t.
|
|
69
|
-
t.spreadElement(
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const wrapperDeclarations = specifiers.map(spec => {
|
|
66
|
+
let importedName, localName;
|
|
67
|
+
|
|
68
|
+
if (t.isImportDefaultSpecifier(spec)) {
|
|
69
|
+
importedName = 'default';
|
|
70
|
+
localName = spec.local.name;
|
|
71
|
+
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
72
|
+
localName = spec.local.name;
|
|
73
|
+
return t.variableDeclarator(
|
|
74
|
+
t.identifier(localName),
|
|
75
|
+
t.conditionalExpression(
|
|
70
76
|
t.callExpression(
|
|
71
|
-
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.
|
|
77
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.HAS)),
|
|
72
78
|
[t.stringLiteral(absolutePath)]
|
|
73
|
-
)
|
|
79
|
+
),
|
|
80
|
+
t.objectExpression([
|
|
81
|
+
t.spreadElement(originalVarName),
|
|
82
|
+
t.spreadElement(
|
|
83
|
+
t.callExpression(
|
|
84
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.GET)),
|
|
85
|
+
[t.stringLiteral(absolutePath)]
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
]),
|
|
89
|
+
originalVarName
|
|
74
90
|
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
importedName = spec.imported.name;
|
|
94
|
+
localName = spec.local.name;
|
|
95
|
+
}
|
|
80
96
|
|
|
81
|
-
const extractDeclarations = specifiers.map(spec => {
|
|
82
|
-
let importedName, localName;
|
|
83
|
-
|
|
84
|
-
if (t.isImportDefaultSpecifier(spec)) {
|
|
85
|
-
importedName = 'default';
|
|
86
|
-
localName = spec.local.name;
|
|
87
|
-
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
88
|
-
localName = spec.local.name;
|
|
89
97
|
return t.variableDeclarator(
|
|
90
98
|
t.identifier(localName),
|
|
91
|
-
|
|
99
|
+
t.arrowFunctionExpression(
|
|
100
|
+
[t.restElement(t.identifier('args'))],
|
|
101
|
+
t.blockStatement([
|
|
102
|
+
t.variableDeclaration(BABEL.CONST, [
|
|
103
|
+
t.variableDeclarator(
|
|
104
|
+
t.identifier(BABEL.MODULE),
|
|
105
|
+
t.conditionalExpression(
|
|
106
|
+
t.callExpression(
|
|
107
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.HAS)),
|
|
108
|
+
[t.stringLiteral(absolutePath)]
|
|
109
|
+
),
|
|
110
|
+
t.objectExpression([
|
|
111
|
+
t.spreadElement(originalVarName),
|
|
112
|
+
t.spreadElement(
|
|
113
|
+
t.callExpression(
|
|
114
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.GET)),
|
|
115
|
+
[t.stringLiteral(absolutePath)]
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
]),
|
|
119
|
+
originalVarName
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
]),
|
|
123
|
+
t.returnStatement(
|
|
124
|
+
t.callExpression(
|
|
125
|
+
t.memberExpression(t.identifier(BABEL.MODULE), t.identifier(importedName)),
|
|
126
|
+
[t.spreadElement(t.identifier('args'))]
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
])
|
|
130
|
+
)
|
|
92
131
|
);
|
|
93
|
-
}
|
|
94
|
-
importedName = spec.imported.name;
|
|
95
|
-
localName = spec.local.name;
|
|
96
|
-
}
|
|
132
|
+
});
|
|
97
133
|
|
|
98
|
-
|
|
99
|
-
t.identifier(localName),
|
|
100
|
-
t.memberExpression(moduleVarName, t.identifier(importedName))
|
|
101
|
-
);
|
|
102
|
-
});
|
|
134
|
+
const wrapperDeclaration = t.variableDeclaration(BABEL.CONST, wrapperDeclarations);
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
nodePath.replaceWithMultiple([originalDeclaration, moduleDeclaration, extractDeclaration]);
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
VariableDeclaration(nodePath, state) {
|
|
110
|
-
const declarations = nodePath.node.declarations;
|
|
111
|
-
|
|
112
|
-
if (!declarations || declarations.length === 0) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (nodePath.node._transformed) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
136
|
+
nodePath.replaceWithMultiple([originalDeclaration, wrapperDeclaration]);
|
|
137
|
+
},
|
|
119
138
|
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
VariableDeclaration(nodePath, state) {
|
|
140
|
+
const declarations = nodePath.node.declarations;
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
if (!declarations || declarations.length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
125
145
|
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
!t.isIdentifier(init.callee, { name: 'require' }) ||
|
|
129
|
-
!init.arguments[0] ||
|
|
130
|
-
!t.isStringLiteral(init.arguments[0])) {
|
|
131
|
-
newDeclarations.push(declarator);
|
|
132
|
-
continue;
|
|
146
|
+
if (nodePath.node._transformed) {
|
|
147
|
+
return;
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
150
|
+
const newDeclarations = [];
|
|
151
|
+
let hasTransformation = false;
|
|
152
|
+
|
|
153
|
+
for (const declarator of declarations) {
|
|
154
|
+
const init = declarator.init;
|
|
155
|
+
|
|
156
|
+
if (!init ||
|
|
157
|
+
!t.isCallExpression(init) ||
|
|
158
|
+
!t.isIdentifier(init.callee, {name: 'require'}) ||
|
|
159
|
+
!init.arguments[0] ||
|
|
160
|
+
!t.isStringLiteral(init.arguments[0])) {
|
|
161
|
+
newDeclarations.push(declarator);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const source = init.arguments[0].value;
|
|
166
|
+
const currentFilePath = state.filename || process.cwd();
|
|
167
|
+
const currentDir = path.dirname(currentFilePath);
|
|
168
|
+
|
|
169
|
+
let absolutePath;
|
|
170
|
+
if (source.startsWith(BABEL.PERIOD)) {
|
|
171
|
+
absolutePath = path.resolve(currentDir, source);
|
|
172
|
+
} else {
|
|
173
|
+
absolutePath = source;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (mockedPaths && !mockedPaths.has(absolutePath)) {
|
|
177
|
+
newDeclarations.push(declarator);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
hasTransformation = true;
|
|
182
|
+
const originalVar = nodePath.scope.generateUidIdentifier('original');
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
|
|
186
|
+
const _original = require('./random');
|
|
187
|
+
const random = (...args) => {
|
|
188
|
+
const module = mockStore.has('/path/to/random.js')
|
|
189
|
+
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
190
|
+
: _original;
|
|
191
|
+
return module.random(...args);
|
|
192
|
+
};
|
|
193
|
+
*/
|
|
139
194
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} else {
|
|
144
|
-
absolutePath = source;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/*
|
|
148
|
-
const _original = require('./random');
|
|
149
|
-
const _module = mockStore.has('/path/to/random.js')
|
|
150
|
-
? { ..._original, ...mockStore.get('/path/to/random.js') }
|
|
151
|
-
: _original;
|
|
152
|
-
*/
|
|
153
|
-
|
|
154
|
-
const originalVar = nodePath.scope.generateUidIdentifier('original');
|
|
155
|
-
const originalDeclarator = t.variableDeclarator(
|
|
156
|
-
originalVar,
|
|
157
|
-
t.callExpression(
|
|
158
|
-
t.identifier('require'),
|
|
159
|
-
[t.stringLiteral(source)]
|
|
160
|
-
)
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const transformedRequire = t.conditionalExpression(
|
|
164
|
-
t.callExpression(
|
|
165
|
-
t.memberExpression(
|
|
166
|
-
t.identifier(MOCK.STORE_NAME),
|
|
167
|
-
t.identifier(BABEL.HAS)
|
|
168
|
-
),
|
|
169
|
-
[t.stringLiteral(absolutePath)]
|
|
170
|
-
),
|
|
171
|
-
t.objectExpression([
|
|
172
|
-
t.spreadElement(originalVar),
|
|
173
|
-
t.spreadElement(
|
|
195
|
+
newDeclarations.push(
|
|
196
|
+
t.variableDeclarator(
|
|
197
|
+
originalVar,
|
|
174
198
|
t.callExpression(
|
|
175
|
-
t.
|
|
176
|
-
|
|
177
|
-
t.identifier(BABEL.GET)
|
|
178
|
-
),
|
|
179
|
-
[t.stringLiteral(absolutePath)]
|
|
199
|
+
t.identifier('require'),
|
|
200
|
+
[t.stringLiteral(source)]
|
|
180
201
|
)
|
|
181
202
|
)
|
|
182
|
-
]),
|
|
183
|
-
originalVar
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
if (t.isObjectPattern(declarator.id) || t.isArrayPattern(declarator.id)) {
|
|
187
|
-
const tempVar = nodePath.scope.generateUidIdentifier('module');
|
|
188
|
-
|
|
189
|
-
newDeclarations.push(originalDeclarator);
|
|
190
|
-
|
|
191
|
-
newDeclarations.push(
|
|
192
|
-
t.variableDeclarator(tempVar, transformedRequire)
|
|
193
203
|
);
|
|
194
204
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
if (t.isObjectPattern(declarator.id)) {
|
|
206
|
+
const properties = declarator.id.properties;
|
|
207
|
+
|
|
208
|
+
properties.forEach(prop => {
|
|
209
|
+
const key = prop.key.name;
|
|
210
|
+
const localName = prop.value.name;
|
|
211
|
+
|
|
212
|
+
newDeclarations.push(
|
|
213
|
+
t.variableDeclarator(
|
|
214
|
+
t.identifier(localName),
|
|
215
|
+
t.arrowFunctionExpression(
|
|
216
|
+
[t.restElement(t.identifier('args'))],
|
|
217
|
+
t.blockStatement([
|
|
218
|
+
t.variableDeclaration(BABEL.CONST, [
|
|
219
|
+
t.variableDeclarator(
|
|
220
|
+
t.identifier(BABEL.MODULE),
|
|
221
|
+
t.conditionalExpression(
|
|
222
|
+
t.callExpression(
|
|
223
|
+
t.memberExpression(
|
|
224
|
+
t.identifier(MOCK.STORE_NAME),
|
|
225
|
+
t.identifier(BABEL.HAS)
|
|
226
|
+
),
|
|
227
|
+
[t.stringLiteral(absolutePath)]
|
|
228
|
+
),
|
|
229
|
+
t.objectExpression([
|
|
230
|
+
t.spreadElement(originalVar),
|
|
231
|
+
t.spreadElement(
|
|
232
|
+
t.callExpression(
|
|
233
|
+
t.memberExpression(
|
|
234
|
+
t.identifier(MOCK.STORE_NAME),
|
|
235
|
+
t.identifier(BABEL.GET)
|
|
236
|
+
),
|
|
237
|
+
[t.stringLiteral(absolutePath)]
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
]),
|
|
241
|
+
originalVar
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
]),
|
|
245
|
+
t.returnStatement(
|
|
246
|
+
t.callExpression(
|
|
247
|
+
t.memberExpression(
|
|
248
|
+
t.identifier(BABEL.MODULE),
|
|
249
|
+
t.identifier(key)
|
|
250
|
+
),
|
|
251
|
+
[t.spreadElement(t.identifier('args'))]
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
])
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
} else {
|
|
260
|
+
newDeclarations.push(
|
|
261
|
+
t.variableDeclarator(
|
|
262
|
+
declarator.id,
|
|
263
|
+
t.arrowFunctionExpression(
|
|
264
|
+
[t.restElement(t.identifier('args'))],
|
|
265
|
+
t.blockStatement([
|
|
266
|
+
t.variableDeclaration(BABEL.CONST, [
|
|
267
|
+
t.variableDeclarator(
|
|
268
|
+
t.identifier(BABEL.MODULE),
|
|
269
|
+
t.conditionalExpression(
|
|
270
|
+
t.callExpression(
|
|
271
|
+
t.memberExpression(
|
|
272
|
+
t.identifier(MOCK.STORE_NAME),
|
|
273
|
+
t.identifier(BABEL.HAS)
|
|
274
|
+
),
|
|
275
|
+
[t.stringLiteral(absolutePath)]
|
|
276
|
+
),
|
|
277
|
+
t.objectExpression([
|
|
278
|
+
t.spreadElement(originalVar),
|
|
279
|
+
t.spreadElement(
|
|
280
|
+
t.callExpression(
|
|
281
|
+
t.memberExpression(
|
|
282
|
+
t.identifier(MOCK.STORE_NAME),
|
|
283
|
+
t.identifier(BABEL.GET)
|
|
284
|
+
),
|
|
285
|
+
[t.stringLiteral(absolutePath)]
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
]),
|
|
289
|
+
originalVar
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
]),
|
|
293
|
+
t.returnStatement(
|
|
294
|
+
t.callExpression(
|
|
295
|
+
t.memberExpression(
|
|
296
|
+
t.identifier(BABEL.MODULE),
|
|
297
|
+
t.identifier('default')
|
|
298
|
+
),
|
|
299
|
+
[t.spreadElement(t.identifier('args'))]
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
])
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
204
307
|
}
|
|
205
|
-
}
|
|
206
308
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
309
|
+
if (hasTransformation) {
|
|
310
|
+
nodePath.node.declarations = newDeclarations;
|
|
311
|
+
nodePath.node._transformed = true;
|
|
312
|
+
}
|
|
210
313
|
}
|
|
211
314
|
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
};
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
};
|
package/bin/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import {findAllSourceFiles, findTestFiles} from "./utils/findFiles.js";
|
|
|
7
7
|
import {green, red, yellow} from "../utils/consoleColor.js";
|
|
8
8
|
import {getTestResultMsg} from "../utils/makeMessage.js";
|
|
9
9
|
import {RESULT_TITLE} from "../constants.js";
|
|
10
|
+
import {collectMockedPaths} from "../src/mock/collectMocks.js";
|
|
10
11
|
|
|
11
12
|
const getUserModuleType = () => {
|
|
12
13
|
try {
|
|
@@ -38,19 +39,20 @@ const main = async () => {
|
|
|
38
39
|
global[key] = jsTe[key];
|
|
39
40
|
});
|
|
40
41
|
|
|
42
|
+
const testFiles = findTestFiles(process.cwd());
|
|
43
|
+
console.log(`\nFound ${green(testFiles.length)} test file(s)`);
|
|
44
|
+
|
|
45
|
+
const mockedPaths = collectMockedPaths(testFiles);
|
|
46
|
+
|
|
41
47
|
const sourceFiles = findAllSourceFiles(process.cwd());
|
|
42
48
|
for (const file of sourceFiles) {
|
|
43
|
-
transformFiles(file);
|
|
49
|
+
transformFiles(file, mockedPaths);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
const testFiles = findTestFiles(process.cwd());
|
|
47
|
-
|
|
48
|
-
console.log(`\nFound ${green(testFiles.length)} test file(s)`);
|
|
49
|
-
|
|
50
52
|
for (const file of testFiles) {
|
|
51
53
|
console.log(`\n${yellow(file)}\n`);
|
|
54
|
+
transformFiles(file, mockedPaths);
|
|
52
55
|
|
|
53
|
-
transformFiles(file);
|
|
54
56
|
await import(path.resolve(file));
|
|
55
57
|
|
|
56
58
|
const {passed, failed} = await jsTe.run();
|
|
@@ -6,13 +6,13 @@ import {red} from "../../utils/consoleColor.js";
|
|
|
6
6
|
|
|
7
7
|
const originalFiles = new Map();
|
|
8
8
|
|
|
9
|
-
export const transformFiles = (filePath) => {
|
|
9
|
+
export const transformFiles = (filePath, mockPath) => {
|
|
10
10
|
const originalCode = fs.readFileSync(filePath, 'utf-8');
|
|
11
11
|
originalFiles.set(filePath, originalCode);
|
|
12
12
|
|
|
13
13
|
const transformed = transformSync(originalCode, {
|
|
14
14
|
filename: filePath,
|
|
15
|
-
plugins: [babelTransformImport],
|
|
15
|
+
plugins: [babelTransformImport(mockPath)],
|
|
16
16
|
parserOpts: {
|
|
17
17
|
sourceType: BABEL.MODULE,
|
|
18
18
|
plugins: ['dynamicImport']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dannysir/js-te",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "JavaScript test library",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./src/mock/store.js": "./src/mock/store.js"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
|
-
"js-te": "
|
|
16
|
+
"js-te": "bin/cli.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"clean": "rimraf dist",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"utils/",
|
|
28
28
|
"src/",
|
|
29
29
|
"babelTransformImport.js",
|
|
30
|
+
"babelCollectMocks.js",
|
|
30
31
|
"constants.js"
|
|
31
32
|
],
|
|
32
33
|
"keywords": [
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
},
|
|
53
54
|
"repository": {
|
|
54
55
|
"type": "git",
|
|
55
|
-
"url": "git+https://github.com/dannysir/js-te-package"
|
|
56
|
+
"url": "git+https://github.com/dannysir/js-te-package.git"
|
|
56
57
|
},
|
|
57
58
|
"bugs": {
|
|
58
59
|
"url": "https://github.com/dannysir/js-te-package/issues"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import {transformSync} from "@babel/core";
|
|
3
|
+
import {BABEL} from "../../constants.js";
|
|
4
|
+
import {createMockCollectorPlugin} from "../../babelCollectMocks.js";
|
|
5
|
+
|
|
6
|
+
export const collectMockedPaths = (testFiles) => {
|
|
7
|
+
const mockedPaths = new Set();
|
|
8
|
+
|
|
9
|
+
for (const testFile of testFiles) {
|
|
10
|
+
const code = fs.readFileSync(testFile, 'utf-8');
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
transformSync(code, {
|
|
14
|
+
filename: testFile,
|
|
15
|
+
plugins: [createMockCollectorPlugin(mockedPaths)],
|
|
16
|
+
parserOpts: {
|
|
17
|
+
sourceType: BABEL.MODULE,
|
|
18
|
+
plugins: ['dynamicImport']
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// 파싱 에러는 무시 (어차피 테스트 실행 시 에러 발생)
|
|
23
|
+
console.warn(`Warning: Failed to scan ${testFile} for mocks`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return mockedPaths;
|
|
28
|
+
};
|