@dannysir/js-te 0.0.2 → 0.1.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 +100 -32
- package/babelTransformImport.js +28 -21
- package/bin/cli.js +43 -29
- package/constants.js +22 -0
- package/index.js +6 -1
- package/package.json +1 -1
- package/src/mock/store.js +5 -5
- package/src/tests.js +43 -1
package/README.md
CHANGED
|
@@ -27,10 +27,12 @@ describe('[단순 연산 테스트]', () => {
|
|
|
27
27
|
|
|
28
28
|
### 2. 테스트 실행
|
|
29
29
|
|
|
30
|
-
package.json에
|
|
30
|
+
package.json에 추가.
|
|
31
31
|
|
|
32
|
+
- type을 module로 설정해주세요.
|
|
32
33
|
```json
|
|
33
34
|
{
|
|
35
|
+
"type": "module",
|
|
34
36
|
"scripts": {
|
|
35
37
|
"test": "js-te"
|
|
36
38
|
}
|
|
@@ -42,6 +44,11 @@ package.json에 추가:
|
|
|
42
44
|
npm test
|
|
43
45
|
```
|
|
44
46
|
|
|
47
|
+
### 예시 출력 화면
|
|
48
|
+
|
|
49
|
+
<p align='center'>
|
|
50
|
+
<img width="585" height="902" alt="스크린샷 2025-11-20 오후 12 22 27" src="https://github.com/user-attachments/assets/3d087a61-cc44-4f5b-8a2f-efd5f15c12b7" />
|
|
51
|
+
</p>
|
|
45
52
|
|
|
46
53
|
# API
|
|
47
54
|
|
|
@@ -127,41 +134,40 @@ Babel을 사용해서 import 구문을 변환하여 mock 함수를 가져오도
|
|
|
127
134
|
2. `path`를 key로 이용해 Map에 저장
|
|
128
135
|
2. Babel로 코드 변환
|
|
129
136
|
1. 전체 파일의 import문 확인
|
|
130
|
-
2.
|
|
131
|
-
|
|
137
|
+
2. (0.0.3 버전 추가) import 경로를 **절대 경로**로 변환
|
|
138
|
+
2. import 경로(절대 경로)가 Map에 존재하면 mock 객체로 변환
|
|
139
|
+
3. import 경로(절대 경로)가 Map에 없다면 그대로 import
|
|
132
140
|
3. 테스트 실행
|
|
133
141
|
4. 원본 파일 복구
|
|
134
142
|
|
|
135
|
-
###
|
|
136
|
-
|
|
137
|
-
mocking 기능의 경우 현재 `path`를 기반으로 직접 변환을 하기 때문에 mocking이 필요한 함수의 경우
|
|
138
|
-
|
|
139
|
-
반드시 **절대 경로**로 표현한 후 `mock` 함수에 **절대 경로**로 등록을 해주세요.
|
|
143
|
+
### `mock(모듈 절대 경로), mock객체)`
|
|
140
144
|
|
|
141
|
-
|
|
145
|
+
모듈을 모킹합니다. import 하기 **전에** 호출해야 합니다.
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
**🚨 주의사항**
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
1. 반드시 경로는 절대 경로로 입력해주세요.
|
|
150
|
+
- babel이 import문에서 절대 경로로 변환하여 확인을 하기 때문에 반드시 절대 경로로 등록해주세요.
|
|
151
|
+
2. import문을 반드시 mocking 이후에 선언해주세요.
|
|
152
|
+
- mocking 전에 import를 하게 되면 mocking되기 전의 모듈을 가져오게 됩니다.
|
|
146
153
|
|
|
147
154
|
```javascript
|
|
148
155
|
// random.js
|
|
149
156
|
export const random = () => Math.random();
|
|
150
157
|
|
|
151
158
|
// game.js
|
|
152
|
-
import { random } from './random.js';
|
|
159
|
+
import { random } from './random.js'; // 자유롭게 import하면 babel에서 절대 경로로 변환하여 판단합니다.
|
|
153
160
|
export const play = () => random() * 10;
|
|
154
161
|
|
|
155
162
|
// game.test.js
|
|
156
|
-
import { mock, test, expect } from 'js-te';
|
|
157
|
-
|
|
158
163
|
test('랜덤 함수 모킹', async () => {
|
|
159
164
|
// 1. 먼저 모킹
|
|
160
|
-
mock('
|
|
165
|
+
mock('/Users/san/Js-Te/test-helper/random.js', { // 반드시 절대 경로로 등록
|
|
161
166
|
random: () => 0.5
|
|
162
167
|
});
|
|
163
168
|
|
|
164
169
|
// 2. 그 다음 import
|
|
170
|
+
// 상단에 import문을 입력할 경우
|
|
165
171
|
const { play } = await import('./game.js');
|
|
166
172
|
|
|
167
173
|
// 3. 모킹된 값 사용
|
|
@@ -181,6 +187,82 @@ test('랜덤 함수 모킹', async () => {
|
|
|
181
187
|
|
|
182
188
|
mock이 등록되어 있는지 확인합니다.
|
|
183
189
|
|
|
190
|
+
## `each(cases)`
|
|
191
|
+
|
|
192
|
+
`cases`를 배열로 받아 순차적으로 테스트 진행
|
|
193
|
+
|
|
194
|
+
#### 🚨 주의 사항
|
|
195
|
+
|
|
196
|
+
`cases`는 반드시 `Array` 타입으로 받아야 합니다.
|
|
197
|
+
|
|
198
|
+
### 플레이스 홀더
|
|
199
|
+
|
|
200
|
+
- %s - 문자열/숫자
|
|
201
|
+
- %o - 객체 (JSON.stringify)
|
|
202
|
+
|
|
203
|
+
```jsx
|
|
204
|
+
test.each([
|
|
205
|
+
[1, 2, 3, 6],
|
|
206
|
+
[3, 4, 5, 12],
|
|
207
|
+
[10, 20, 13, 43],
|
|
208
|
+
[10, 12, 13, 35],
|
|
209
|
+
])('[each test] - input : %s, %s, %s, %s', (a, b, c, result) => {
|
|
210
|
+
expect(a + b + c).toBe(result);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
/* 출력 결과
|
|
214
|
+
✓ [each test] - input : 1, 2, 3, 6
|
|
215
|
+
✓ [each test] - input : 3, 4, 5, 12
|
|
216
|
+
✓ [each test] - input : 10, 20, 13, 43
|
|
217
|
+
✓ [each test] - input : 10, 12, 13, 35
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
test.each([
|
|
221
|
+
[{ name : 'dannysir', age : null}],
|
|
222
|
+
])('[each test placeholder] - input : %o', (arg) => {
|
|
223
|
+
expect(arg.name).toBe('dannysir');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/* 출력 결과
|
|
227
|
+
✓ [each test placeholder] - input : {"name":"dannysir","age":null}
|
|
228
|
+
*/
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## `beforeEach(함수)`
|
|
232
|
+
|
|
233
|
+
각 테스트가 진행되기 전에 실행할 함수를 선언합니다.
|
|
234
|
+
|
|
235
|
+
중첩된 describe에서의 `beforeEach`는 상위 describe의 `beforeEach`를 모두 실행한 후, 자신의 `beforeEach`를 실행합니다.
|
|
236
|
+
|
|
237
|
+
```jsx
|
|
238
|
+
describe('카운터 테스트', () => {
|
|
239
|
+
let counter;
|
|
240
|
+
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
counter = 0;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('카운터 증가', () => {
|
|
246
|
+
counter++;
|
|
247
|
+
expect(counter).toBe(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('카운터는 0부터 시작', () => {
|
|
251
|
+
expect(counter).toBe(0);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('중첩된 describe', () => {
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
counter = 10;
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('카운터는 10', () => {
|
|
260
|
+
expect(counter).toBe(10);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
184
266
|
## 테스트 파일 찾기 규칙
|
|
185
267
|
|
|
186
268
|
자동으로 다음 파일들을 찾아서 실행합니다:
|
|
@@ -204,8 +286,6 @@ mock이 등록되어 있는지 확인합니다.
|
|
|
204
286
|
### 기본 테스트
|
|
205
287
|
|
|
206
288
|
```javascript
|
|
207
|
-
import { describe, test, expect } from 'js-te';
|
|
208
|
-
|
|
209
289
|
describe('문자열 테스트', () => {
|
|
210
290
|
test('문자열 합치기', () => {
|
|
211
291
|
const result = 'hello' + ' ' + 'world';
|
|
@@ -222,10 +302,8 @@ describe('문자열 테스트', () => {
|
|
|
222
302
|
|
|
223
303
|
```javascript
|
|
224
304
|
// mocking.test.js
|
|
225
|
-
import { mock, test, expect } from 'js-te';
|
|
226
|
-
|
|
227
305
|
test('[mocking] - mocking random function', async () => {
|
|
228
|
-
mock('/
|
|
306
|
+
mock('/Users/san/Js-Te/test-helper/random.js', {
|
|
229
307
|
random: () => 3,
|
|
230
308
|
});
|
|
231
309
|
const {play} = await import('../src/test-helper/game.js');
|
|
@@ -234,7 +312,7 @@ test('[mocking] - mocking random function', async () => {
|
|
|
234
312
|
|
|
235
313
|
|
|
236
314
|
// game.js
|
|
237
|
-
import {random} from '/
|
|
315
|
+
import {random} from '/test-helper/random.js'
|
|
238
316
|
|
|
239
317
|
export const play = () => {
|
|
240
318
|
return random() * 10;
|
|
@@ -244,16 +322,6 @@ export const play = () => {
|
|
|
244
322
|
export const random = () => Math.random();
|
|
245
323
|
```
|
|
246
324
|
|
|
247
|
-
## 설정
|
|
248
|
-
|
|
249
|
-
`package.json`에 해당 설정을 하셔야 정상 작동합니다.
|
|
250
|
-
|
|
251
|
-
```json
|
|
252
|
-
{
|
|
253
|
-
"type": "module"
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
325
|
## 링크
|
|
258
326
|
|
|
259
327
|
- [GitHub](https://github.com/dannysir/Js-Te)
|
|
@@ -264,4 +332,4 @@ export const random = () => Math.random();
|
|
|
264
332
|
|
|
265
333
|
## 라이선스
|
|
266
334
|
|
|
267
|
-
ISC
|
|
335
|
+
ISC
|
package/babelTransformImport.js
CHANGED
|
@@ -1,44 +1,53 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {BABEL, MOCK} from "./constants.js";
|
|
3
|
+
|
|
1
4
|
export const babelTransformImport = ({types: t}) => {
|
|
2
5
|
return {
|
|
3
6
|
visitor: {
|
|
4
7
|
Program(path) {
|
|
5
8
|
const importStatement = t.ImportDeclaration(
|
|
6
9
|
[t.importSpecifier(
|
|
7
|
-
t.identifier(
|
|
8
|
-
t.identifier(
|
|
10
|
+
t.identifier(MOCK.STORE_NAME),
|
|
11
|
+
t.identifier(MOCK.STORE_NAME)
|
|
9
12
|
)],
|
|
10
|
-
t.stringLiteral(
|
|
13
|
+
t.stringLiteral(MOCK.STORE_PATH)
|
|
11
14
|
);
|
|
12
15
|
path.node.body.unshift(importStatement);
|
|
13
16
|
},
|
|
14
17
|
|
|
15
|
-
ImportDeclaration(
|
|
16
|
-
const source =
|
|
18
|
+
ImportDeclaration(nodePath, state) {
|
|
19
|
+
const source = nodePath.node.source.value;
|
|
17
20
|
|
|
18
|
-
if (source ===
|
|
21
|
+
if (source === MOCK.STORE_PATH) {
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const
|
|
25
|
+
const currentFilePath = state.filename || process.cwd();
|
|
26
|
+
const currentDir = path.dirname(currentFilePath);
|
|
27
|
+
|
|
28
|
+
let absolutePath;
|
|
29
|
+
if (source.startsWith(BABEL.PERIOD)) {
|
|
30
|
+
absolutePath = path.resolve(currentDir, source);
|
|
31
|
+
} else {
|
|
32
|
+
absolutePath = source;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const specifiers = nodePath.node.specifiers;
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
const moduleVarName = path.scope.generateUidIdentifier('module');
|
|
37
|
+
const moduleVarName = nodePath.scope.generateUidIdentifier(BABEL.MODULE);
|
|
26
38
|
|
|
27
|
-
const moduleDeclaration = t.variableDeclaration(
|
|
39
|
+
const moduleDeclaration = t.variableDeclaration(BABEL.CONST, [
|
|
28
40
|
t.variableDeclarator(
|
|
29
41
|
moduleVarName,
|
|
30
42
|
t.conditionalExpression(
|
|
31
|
-
// 조건: __mockRegistry__.has(source)
|
|
32
43
|
t.callExpression(
|
|
33
|
-
t.memberExpression(t.identifier(
|
|
34
|
-
[t.stringLiteral(
|
|
44
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.HAS)),
|
|
45
|
+
[t.stringLiteral(absolutePath)]
|
|
35
46
|
),
|
|
36
|
-
// true: mock 반환
|
|
37
47
|
t.callExpression(
|
|
38
|
-
t.memberExpression(t.identifier(
|
|
39
|
-
[t.stringLiteral(
|
|
48
|
+
t.memberExpression(t.identifier(MOCK.STORE_NAME), t.identifier(BABEL.GET)),
|
|
49
|
+
[t.stringLiteral(absolutePath)]
|
|
40
50
|
),
|
|
41
|
-
// false: 실제 import
|
|
42
51
|
t.awaitExpression(
|
|
43
52
|
t.importExpression(t.stringLiteral(source))
|
|
44
53
|
)
|
|
@@ -46,7 +55,6 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
46
55
|
)
|
|
47
56
|
]);
|
|
48
57
|
|
|
49
|
-
// 2. 각 specifier를 moduleVarName에서 추출
|
|
50
58
|
const extractDeclarations = specifiers.map(spec => {
|
|
51
59
|
let importedName, localName;
|
|
52
60
|
|
|
@@ -55,7 +63,6 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
55
63
|
localName = spec.local.name;
|
|
56
64
|
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
57
65
|
localName = spec.local.name;
|
|
58
|
-
// namespace import는 전체 모듈
|
|
59
66
|
return t.variableDeclarator(
|
|
60
67
|
t.identifier(localName),
|
|
61
68
|
moduleVarName
|
|
@@ -71,9 +78,9 @@ export const babelTransformImport = ({types: t}) => {
|
|
|
71
78
|
);
|
|
72
79
|
});
|
|
73
80
|
|
|
74
|
-
const extractDeclaration = t.variableDeclaration(
|
|
81
|
+
const extractDeclaration = t.variableDeclaration(BABEL.CONST, extractDeclarations);
|
|
75
82
|
|
|
76
|
-
|
|
83
|
+
nodePath.replaceWithMultiple([moduleDeclaration, extractDeclaration]);
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
};
|
package/bin/cli.js
CHANGED
|
@@ -4,9 +4,9 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import {transformSync} from '@babel/core';
|
|
6
6
|
import * as jsTe from '../index.js';
|
|
7
|
-
import {green, yellow} from "../utils/consoleColor.js";
|
|
7
|
+
import {green, red, yellow} from "../utils/consoleColor.js";
|
|
8
8
|
import {getTestResultMsg} from "../utils/makeMessage.js";
|
|
9
|
-
import {RESULT_TITLE} from "../constants.js";
|
|
9
|
+
import {BABEL, PATH, RESULT_TITLE} from "../constants.js";
|
|
10
10
|
import { babelTransformImport } from '../babelTransformImport.js';
|
|
11
11
|
|
|
12
12
|
let totalPassed = 0;
|
|
@@ -25,7 +25,7 @@ const transformFile = (filePath) => {
|
|
|
25
25
|
filename: filePath,
|
|
26
26
|
plugins: [babelTransformImport],
|
|
27
27
|
parserOpts: {
|
|
28
|
-
sourceType:
|
|
28
|
+
sourceType: BABEL.MODULE,
|
|
29
29
|
plugins: ['dynamicImport']
|
|
30
30
|
}
|
|
31
31
|
});
|
|
@@ -35,8 +35,13 @@ const transformFile = (filePath) => {
|
|
|
35
35
|
|
|
36
36
|
const restoreFiles = () => {
|
|
37
37
|
for (const [filePath, originalCode] of originalFiles.entries()) {
|
|
38
|
-
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(filePath, originalCode);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(red(`Failed to restore ${filePath}: ${error.message}`));
|
|
42
|
+
}
|
|
39
43
|
}
|
|
44
|
+
originalFiles.clear();
|
|
40
45
|
};
|
|
41
46
|
|
|
42
47
|
const findTestFiles = (dir) => {
|
|
@@ -46,18 +51,18 @@ const findTestFiles = (dir) => {
|
|
|
46
51
|
const items = fs.readdirSync(directory);
|
|
47
52
|
const dirName = path.basename(directory);
|
|
48
53
|
|
|
49
|
-
const isTestDir = dirName ===
|
|
54
|
+
const isTestDir = dirName === PATH.TEST_DIRECTORY || inTestDir;
|
|
50
55
|
|
|
51
56
|
for (const item of items) {
|
|
52
|
-
if (item ===
|
|
57
|
+
if (item === PATH.NODE_MODULES) continue;
|
|
53
58
|
|
|
54
59
|
const fullPath = path.join(directory, item);
|
|
55
60
|
const stat = fs.statSync(fullPath);
|
|
56
61
|
|
|
57
62
|
if (stat.isDirectory()) {
|
|
58
63
|
walk(fullPath, isTestDir);
|
|
59
|
-
} else if (item.endsWith(
|
|
60
|
-
if (item.endsWith(
|
|
64
|
+
} else if (item.endsWith(PATH.TEST_FILE) || isTestDir) {
|
|
65
|
+
if (item.endsWith(PATH.JAVASCRIPT_FILE)) {
|
|
61
66
|
files.push(fullPath);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -75,14 +80,14 @@ const findAllSourceFiles = (dir) => {
|
|
|
75
80
|
const items = fs.readdirSync(directory);
|
|
76
81
|
|
|
77
82
|
for (const item of items) {
|
|
78
|
-
if (item ===
|
|
83
|
+
if (item === PATH.NODE_MODULES || item === PATH.BIN || item === PATH.TEST_DIRECTORY) continue;
|
|
79
84
|
|
|
80
85
|
const fullPath = path.join(directory, item);
|
|
81
86
|
const stat = fs.statSync(fullPath);
|
|
82
87
|
|
|
83
88
|
if (stat.isDirectory()) {
|
|
84
89
|
walk(fullPath);
|
|
85
|
-
} else if (item.endsWith(
|
|
90
|
+
} else if (item.endsWith(PATH.JAVASCRIPT_FILE) && !item.endsWith(PATH.TEST_FILE)) {
|
|
86
91
|
files.push(fullPath);
|
|
87
92
|
}
|
|
88
93
|
}
|
|
@@ -92,32 +97,41 @@ const findAllSourceFiles = (dir) => {
|
|
|
92
97
|
return files;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
const sourceFiles = findAllSourceFiles(process.cwd());
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
const main = async () => {
|
|
102
|
+
try {
|
|
103
|
+
const sourceFiles = findAllSourceFiles(process.cwd());
|
|
104
|
+
for (const file of sourceFiles) {
|
|
105
|
+
transformFile(file);
|
|
106
|
+
}
|
|
100
107
|
|
|
101
|
-
const testFiles = findTestFiles(process.cwd());
|
|
108
|
+
const testFiles = findTestFiles(process.cwd());
|
|
102
109
|
|
|
103
|
-
console.log(`\nFound ${green(testFiles.length)} test file(s)`);
|
|
110
|
+
console.log(`\nFound ${green(testFiles.length)} test file(s)`);
|
|
104
111
|
|
|
105
|
-
for (const file of testFiles) {
|
|
106
|
-
|
|
112
|
+
for (const file of testFiles) {
|
|
113
|
+
console.log(`\n${yellow(file)}\n`);
|
|
107
114
|
|
|
108
|
-
|
|
115
|
+
transformFile(file);
|
|
116
|
+
await import(path.resolve(file));
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
const {passed, failed} = await jsTe.run();
|
|
119
|
+
totalPassed += passed;
|
|
120
|
+
totalFailed += failed;
|
|
121
|
+
}
|
|
111
122
|
|
|
112
|
-
|
|
113
|
-
totalPassed += passed;
|
|
114
|
-
totalFailed += failed;
|
|
115
|
-
}
|
|
123
|
+
console.log(getTestResultMsg(RESULT_TITLE.TOTAL, totalPassed, totalFailed));
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
return totalFailed > 0 ? 1 : 0;
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.log(red('\n✗ Test execution failed'));
|
|
129
|
+
console.log(red(` Error: ${error.message}\n`));
|
|
130
|
+
return 1;
|
|
131
|
+
} finally {
|
|
132
|
+
restoreFiles();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
136
|
+
const exitCode = await main();
|
|
137
|
+
process.exit(exitCode);
|
package/constants.js
CHANGED
|
@@ -25,4 +25,26 @@ export const COLORS = {
|
|
|
25
25
|
cyan: '\x1b[36m',
|
|
26
26
|
gray: '\x1b[90m',
|
|
27
27
|
bold: '\x1b[1m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const MOCK = {
|
|
31
|
+
STORE_NAME: 'mockStore',
|
|
32
|
+
STORE_PATH : '@dannysir/js-te/src/mock/store.js'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const BABEL = {
|
|
36
|
+
MODULE: 'module',
|
|
37
|
+
CONST: 'const',
|
|
38
|
+
HAS: 'has',
|
|
39
|
+
GET: 'get',
|
|
40
|
+
PERIOD: '.',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const PATH = {
|
|
44
|
+
NODE_MODULES: 'node_modules',
|
|
45
|
+
TEST_DIRECTORY: 'test',
|
|
46
|
+
TEST_FILE: '.test.js',
|
|
47
|
+
JAVASCRIPT_FILE: '.js',
|
|
48
|
+
BIN: 'bin',
|
|
49
|
+
|
|
28
50
|
};
|
package/index.js
CHANGED
|
@@ -6,15 +6,19 @@ import {
|
|
|
6
6
|
import {Tests} from "./src/tests.js";
|
|
7
7
|
import {green, red} from "./utils/consoleColor.js";
|
|
8
8
|
import {getTestResultMsg} from "./utils/makeMessage.js";
|
|
9
|
+
import {clearAllMocks} from "./src/mock/store.js";
|
|
9
10
|
|
|
10
11
|
const tests = new Tests();
|
|
11
12
|
|
|
12
13
|
export { mock, clearAllMocks, unmock, isMocked } from './src/mock/store.js';
|
|
13
14
|
|
|
14
15
|
export const test = (description, fn) => tests.test(description, fn);
|
|
16
|
+
test.each = (cases) => tests.testEach(cases);
|
|
15
17
|
|
|
16
18
|
export const describe = (suiteName, fn) => tests.describe(suiteName, fn);
|
|
17
19
|
|
|
20
|
+
export const beforeEach = (fn) => tests.beforeEach(fn);
|
|
21
|
+
|
|
18
22
|
export const expect = (actual) => {
|
|
19
23
|
let value = actual;
|
|
20
24
|
|
|
@@ -73,10 +77,11 @@ export const run = async () => {
|
|
|
73
77
|
const directoryString = green(CHECK) + (test.path === '' ? EMPTY : test.path + DIRECTORY_DELIMITER) + test.description
|
|
74
78
|
console.log(directoryString);
|
|
75
79
|
passed++;
|
|
80
|
+
clearAllMocks();
|
|
76
81
|
} catch (error) {
|
|
77
82
|
const errorDirectory = red(CROSS) + test.path + test.description
|
|
78
83
|
console.log(errorDirectory);
|
|
79
|
-
console.log(red(` ${error.message}`));
|
|
84
|
+
console.log(red(` Error Message : ${error.message}`));
|
|
80
85
|
failed++;
|
|
81
86
|
}
|
|
82
87
|
}
|
package/package.json
CHANGED
package/src/mock/store.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const mockStore = new Map();
|
|
2
2
|
|
|
3
3
|
export function clearAllMocks() {
|
|
4
|
-
|
|
4
|
+
mockStore.clear();
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function mock(modulePath, mockExports) {
|
|
8
|
-
|
|
8
|
+
mockStore.set(modulePath, mockExports);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function unmock(modulePath) {
|
|
12
|
-
|
|
12
|
+
mockStore.delete(modulePath);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function isMocked(modulePath) {
|
|
16
|
-
return
|
|
16
|
+
return mockStore.has(modulePath);
|
|
17
17
|
}
|
package/src/tests.js
CHANGED
|
@@ -3,22 +3,45 @@ import {DIRECTORY_DELIMITER} from "../constants.js";
|
|
|
3
3
|
export class Tests {
|
|
4
4
|
#tests = [];
|
|
5
5
|
#testDepth = [];
|
|
6
|
+
#beforeEachArr = [];
|
|
6
7
|
|
|
7
8
|
describe(str, fn) {
|
|
8
9
|
this.#testDepth.push(str);
|
|
10
|
+
const prevLength = this.#beforeEachArr.length;
|
|
9
11
|
fn();
|
|
12
|
+
this.#beforeEachArr.length = prevLength;
|
|
10
13
|
this.#testDepth.pop();
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
test(description, fn) {
|
|
17
|
+
const beforeEachHooks = [...this.#beforeEachArr];
|
|
18
|
+
|
|
14
19
|
const testObj = {
|
|
15
20
|
description,
|
|
16
|
-
fn
|
|
21
|
+
fn: async () => {
|
|
22
|
+
for (const hook of beforeEachHooks) {
|
|
23
|
+
await hook();
|
|
24
|
+
}
|
|
25
|
+
await fn();
|
|
26
|
+
},
|
|
17
27
|
path: this.#testDepth.join(DIRECTORY_DELIMITER),
|
|
18
28
|
}
|
|
19
29
|
this.#tests.push(testObj);
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
testEach(cases) {
|
|
33
|
+
return (description, fn) => {
|
|
34
|
+
cases.forEach(testCase => {
|
|
35
|
+
const args = Array.isArray(testCase) ? testCase : [testCase];
|
|
36
|
+
this.test(this.#formatDescription(args, description), () => fn(...args));
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(fn) {
|
|
42
|
+
this.#beforeEachArr.push(fn);
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
getTests() {
|
|
23
46
|
return [...this.#tests];
|
|
24
47
|
}
|
|
@@ -26,5 +49,24 @@ export class Tests {
|
|
|
26
49
|
clearTests() {
|
|
27
50
|
this.#tests = [];
|
|
28
51
|
this.#testDepth = [];
|
|
52
|
+
this.#beforeEachArr = [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#formatDescription(args, description) {
|
|
56
|
+
let argIndex = 0;
|
|
57
|
+
return description.replace(/%([so])/g, (match, type) => {
|
|
58
|
+
if (argIndex >= args.length) return match;
|
|
59
|
+
|
|
60
|
+
const arg = args[argIndex++];
|
|
61
|
+
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 's':
|
|
64
|
+
return arg;
|
|
65
|
+
case 'o':
|
|
66
|
+
return JSON.stringify(arg);
|
|
67
|
+
default:
|
|
68
|
+
return match;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
29
71
|
}
|
|
30
72
|
}
|