Skip to content

Commit dad894b

Browse files
authored
feat: support mock withImplementation (#128)
1 parent 239dadb commit dad894b

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

packages/core/src/runtime/api/spy.ts

+31
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,37 @@ const wrapSpy = <T extends FunctionLike>(
3535
: implementation;
3636
};
3737

38+
function withImplementation(fn: T, cb: () => void): void;
39+
function withImplementation(fn: T, cb: () => Promise<void>): Promise<void>;
40+
function withImplementation(
41+
fn: T,
42+
cb: () => void | Promise<void>,
43+
): void | Promise<void> {
44+
const originalImplementation = implementation;
45+
const originalMockImplementationOnce = mockImplementationOnce;
46+
47+
implementation = fn;
48+
mockImplementationOnce = [];
49+
spyState.willCall(willCall);
50+
51+
const reset = () => {
52+
implementation = originalImplementation;
53+
mockImplementationOnce = originalMockImplementationOnce;
54+
};
55+
56+
const result = cb();
57+
58+
if (result instanceof Promise) {
59+
return result.then(() => {
60+
reset();
61+
});
62+
}
63+
64+
reset();
65+
}
66+
67+
spyFn.withImplementation = withImplementation;
68+
3869
spyFn.mockImplementation = (fn) => {
3970
implementation = fn;
4071
return spyFn;

packages/core/src/types/mock.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ export interface MockInstance<T extends FunctionLike = FunctionLike> {
8989
getMockImplementation(): T | undefined;
9090
mockImplementation(fn: T): this;
9191
mockImplementationOnce(fn: T): this;
92-
// withImplementation<T2>(
93-
// fn: T,
94-
// callback: () => T2,
95-
// ): T2 extends Promise<unknown> ? Promise<void> : void;
92+
withImplementation<T2>(
93+
fn: T,
94+
callback: () => T2,
95+
): T2 extends Promise<unknown> ? Promise<void> : void;
9696
// mockReturnThis(): this;
9797
// mockReturnValue(value: ReturnType<T>): this;
9898
// mockReturnValueOnce(value: ReturnType<T>): this;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { describe, expect, it, rstest } from '@rstest/core';
2+
3+
describe('test withImplementation', () => {
4+
it('withImplementation', () => {
5+
let isMockCalled = false;
6+
const mockFn = () => {
7+
isMockCalled = true;
8+
console.log('[call original]');
9+
return 'original';
10+
};
11+
const myMockFn = rstest.fn(mockFn);
12+
13+
const mockFn1 = () => {
14+
isMockCalled = true;
15+
console.log('[call original - 1]');
16+
return 'original - 1';
17+
};
18+
19+
myMockFn.mockImplementationOnce(mockFn1);
20+
21+
myMockFn.withImplementation(
22+
() => {
23+
console.log('[call temp]');
24+
return 'temp';
25+
},
26+
() => {
27+
console.log('[call callback]');
28+
const res = myMockFn();
29+
const res1 = myMockFn();
30+
console.log('[callback res]', res, res1);
31+
},
32+
);
33+
34+
expect(myMockFn.getMockImplementation()).toBe(mockFn1);
35+
expect(isMockCalled).toBe(false);
36+
37+
console.log('[call myMockFn]');
38+
expect(myMockFn()).toBe('original - 1');
39+
expect(isMockCalled).toBe(true);
40+
41+
console.log('[call myMockFn - 1]');
42+
43+
expect(myMockFn()).toBe('original');
44+
expect(myMockFn).toHaveBeenCalledTimes(4);
45+
});
46+
47+
it('withImplementation async', async () => {
48+
const myMockFn = rstest.fn(() => {
49+
console.log('[1 - call original]');
50+
return 'original';
51+
});
52+
53+
await myMockFn.withImplementation(
54+
() => {
55+
console.log('[1 - call temp]');
56+
return 'temp';
57+
},
58+
async () => {
59+
console.log('[1 - call callback]');
60+
const res = myMockFn();
61+
console.log('[1 - callback res]', res);
62+
},
63+
);
64+
65+
console.log('[1 - call myMockFn]');
66+
67+
expect(myMockFn()).toBe('original');
68+
69+
console.log('[1 - call myMockFn - 1]');
70+
expect(myMockFn()).toBe('original');
71+
});
72+
});

tests/spy/withImplementation.test.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { dirname } from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
import { describe, expect, it } from '@rstest/core';
4+
import { runRstestCli } from '../scripts';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = dirname(__filename);
8+
9+
it('test withImplementation', async () => {
10+
const { cli } = await runRstestCli({
11+
command: 'rstest',
12+
args: ['run', 'fixtures/withImplementation.test'],
13+
options: {
14+
nodeOptions: {
15+
cwd: __dirname,
16+
},
17+
},
18+
});
19+
20+
await cli.exec;
21+
expect(cli.exec.process?.exitCode).toBe(0);
22+
const logs = cli.stdout.split('\n').filter(Boolean);
23+
24+
expect(logs.filter((log) => log.startsWith('['))).toMatchInlineSnapshot(`
25+
[
26+
"[call callback]",
27+
"[call temp]",
28+
"[call temp]",
29+
"[callback res] temp temp",
30+
"[call myMockFn]",
31+
"[call original - 1]",
32+
"[call myMockFn - 1]",
33+
"[call original]",
34+
"[1 - call callback]",
35+
"[1 - call temp]",
36+
"[1 - callback res] temp",
37+
"[1 - call myMockFn]",
38+
"[1 - call original]",
39+
"[1 - call myMockFn - 1]",
40+
"[1 - call original]",
41+
]
42+
`);
43+
});

0 commit comments

Comments
 (0)