Skip to content

Commit bbe6df9

Browse files
authored
feat: implement setup and teardown (#77)
part of #63
1 parent 7309333 commit bbe6df9

File tree

16 files changed

+292
-29
lines changed

16 files changed

+292
-29
lines changed

assembly/env.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ export namespace assertResult {
1212
export declare function registerTestFunction(index: u32): void;
1313

1414

15+
@external("__unittest_framework_env","registerBeforeEachFunction")
16+
export declare function registerBeforeEachFunction(index: u32): boolean;
17+
18+
19+
@external("__unittest_framework_env","registerAfterEachFunction")
20+
export declare function registerAfterEachFunction(index: u32): boolean;
21+
22+
1523
@external("__unittest_framework_env","collectCheckResult")
1624
export declare function collectCheckResult(
1725
result: bool,

assembly/implement.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ export function testImpl(name: string, testFunction: () => void): void {
1515
assertResult.removeDescription();
1616
}
1717

18-
export function beforeEachImpl(func: () => void): void {}
18+
export function beforeEachImpl(func: () => void): void {
19+
const result = assertResult.registerBeforeEachFunction(func.index);
20+
assert(result, "register setup function failed");
21+
}
1922

20-
export function afterEachImpl(func: () => void): void {}
23+
export function afterEachImpl(func: () => void): void {
24+
const result = assertResult.registerAfterEachFunction(func.index);
25+
assert(result, "register teardown function failed");
26+
}
2127

2228
export function mockImpl<T extends Function>(
2329
originalFunction: T,

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default defineConfig({
3232
{ text: "Configuration", link: "/api-documents/configuration" },
3333
{ text: "Options", link: "/api-documents/options" },
3434
{ text: "Matchers", link: "/api-documents/matchers" },
35+
{ text: "Setup Teardown", link: "/api-documents/setup-teardown" },
3536
{ text: "Mock Function", link: "/api-documents/mock-function" },
3637
{ text: "Report", link: "/api-documents/coverage-report" },
3738
{ text: "Return Code", link: "/api-documents/return-code.md" },
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## Setup And Teardown
2+
3+
Often while writing tests you have some setup work that needs to happen before tests run, and you have some finishing work that needs to happen after tests run. unittest framework provides helper functions to handle this.
4+
5+
If you have some work you need to do repeatedly for many tests, you can use `beforeEach` and `afterEach` hooks.
6+
7+
::: info
8+
`beforeEach` and `afterEach` can only work inside describe which will limit its scope
9+
:::
10+
11+
### How to Use
12+
13+
```ts
14+
let setup = 0;
15+
describe("setup", () => {
16+
// effect for the whole describe including sub-describe
17+
beforeEach(() => {
18+
setup = 10;
19+
});
20+
test("1st", () => {
21+
expect(setup).equal(10);
22+
setup = 100;
23+
});
24+
test("2nd", () => {
25+
expect(setup).equal(10);
26+
setup = 100;
27+
});
28+
test("3nd", () => {
29+
expect(setup).equal(10);
30+
});
31+
});
32+
```
33+
34+
:::info
35+
If multiple `beforeEach` or `afterEach` is registered, they will be call in order.
36+
:::

docs/release-note.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Improved the as-test performances.
88
- Introduce new features `isolated: false` to significantly reduce test execution time in large projects. ([#73](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/73))
9+
- Introduce setup and teardown API. ([#77](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/77))
910

1011
## 1.3.1
1112

src/core/execute.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,30 +69,36 @@ async function nodeExecutor(
6969
throw new Error("node executor abort");
7070
};
7171

72-
try {
73-
executionRecorder.startTestFunction(`${instrumentResult.baseName} - init`);
74-
wasi.start(ins);
75-
} catch (error) {
76-
await exceptionHandler(error);
77-
}
78-
executionRecorder.finishTestFunction();
72+
await executionRecorder.runTestFunction(
73+
`${instrumentResult.baseName} - init`,
74+
() => {
75+
wasi.start(ins);
76+
},
77+
exceptionHandler
78+
);
7979

80-
const execTestFunction = ins.exports["executeTestFunction"];
80+
const execTestFunction = ins.exports["executeTestFunction"] as (a: number) => void;
8181
assert(typeof execTestFunction === "function");
8282

8383
for (const testCase of executionRecorder.testCases) {
8484
if (isCrashed) {
8585
break;
8686
}
87-
const { fullName, functionIndex } = testCase;
87+
const { fullName, functionIndex, setupFunctions, teardownFunctions } = testCase;
8888
if (matchedTestNames.length === 0 || matchedTestNames.includes(fullName)) {
89-
executionRecorder.startTestFunction(fullName);
90-
try {
91-
(execTestFunction as (a: number) => void)(functionIndex);
92-
} catch (error) {
93-
await exceptionHandler(error);
94-
}
95-
executionRecorder.finishTestFunction();
89+
await executionRecorder.runTestFunction(
90+
fullName,
91+
() => {
92+
for (const setupFuncIndex of setupFunctions) {
93+
execTestFunction(setupFuncIndex);
94+
}
95+
execTestFunction(functionIndex);
96+
for (const teardownFuncIndex of teardownFunctions) {
97+
execTestFunction(teardownFuncIndex);
98+
}
99+
},
100+
exceptionHandler
101+
);
96102
mockStatusRecorder.clear();
97103
}
98104
}

src/core/executionRecorder.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,21 @@ export class ExecutionResult implements IExecutionResult {
4646

4747
class TestBlock {
4848
constructor(public description: string) {}
49+
setupFunctions: number[] = [];
50+
teardownFunctions: number[] = [];
4951
}
5052

51-
class TestCase {
53+
export class TestCase {
5254
fullName: string;
55+
setupFunctions: number[];
56+
teardownFunctions: number[];
5357
constructor(
5458
testBlockStack: TestBlock[],
5559
public functionIndex: number
5660
) {
5761
this.fullName = testBlockStack.map((block) => block.description).join(" ");
62+
this.setupFunctions = testBlockStack.flatMap((block) => block.setupFunctions);
63+
this.teardownFunctions = testBlockStack.flatMap((block) => block.teardownFunctions);
5864
}
5965
}
6066

@@ -73,22 +79,62 @@ export class ExecutionRecorder implements UnitTestFramework {
7379
_removeDescription(): void {
7480
this.testBlockStack.pop();
7581
}
82+
83+
get lastTestBlock(): TestBlock | undefined {
84+
return this.testBlockStack.at(-1);
85+
}
86+
// return false if error
87+
_registerSetup(functionIndex: number): boolean {
88+
const lastTestBlock = this.lastTestBlock;
89+
if (lastTestBlock === undefined) {
90+
return false;
91+
} else {
92+
lastTestBlock.setupFunctions.push(functionIndex);
93+
return true;
94+
}
95+
}
96+
// return false if error
97+
_registerTeardown(functionIndex: number): boolean {
98+
const lastTestBlock = this.lastTestBlock;
99+
if (lastTestBlock === undefined) {
100+
return false;
101+
} else {
102+
lastTestBlock.teardownFunctions.push(functionIndex);
103+
return true;
104+
}
105+
}
76106
_addTestCase(functionIndex: number): void {
77107
this.testCases.push(new TestCase(this.testBlockStack, functionIndex));
78108
}
79109

80-
startTestFunction(testCaseFullName: string): void {
81-
this.currentExecutedTestCaseFullName = testCaseFullName;
110+
_startTestFunction(fullName: string): void {
111+
this.currentExecutedTestCaseFullName = fullName;
82112
this.logRecorder.reset();
83113
}
84-
finishTestFunction(): void {
114+
_finishTestFunction(): void {
85115
const logMessages: string[] | null = this.logRecorder.onFinishTest();
86116
if (logMessages !== null) {
87117
this.result.failedLogMessages[this.currentExecutedTestCaseFullName] = (
88118
this.result.failedLogMessages[this.currentExecutedTestCaseFullName] || []
89119
).concat(logMessages);
90120
}
91121
}
122+
async runTestFunction(
123+
fullName: string,
124+
runner: () => Promise<void> | void,
125+
exceptionHandler: (error: unknown) => Promise<void>
126+
) {
127+
this._startTestFunction(fullName);
128+
try {
129+
const r = runner();
130+
if (r instanceof Promise) {
131+
await r;
132+
}
133+
} catch (error) {
134+
await exceptionHandler(error);
135+
}
136+
this._finishTestFunction();
137+
}
92138

93139
notifyTestCrash(error: ExecutionError): void {
94140
this.logRecorder.addLog(`Reason: ${chalk.red(error.message)}`);
@@ -128,6 +174,12 @@ export class ExecutionRecorder implements UnitTestFramework {
128174
registerTestFunction: (index: number): void => {
129175
this._addTestCase(index);
130176
},
177+
registerBeforeEachFunction: (index: number): boolean => {
178+
return this._registerSetup(index);
179+
},
180+
registerAfterEachFunction: (index: number): boolean => {
181+
return this._registerTeardown(index);
182+
},
131183
collectCheckResult: (result: number, codeInfoIndex: number, actualValue: number, expectValue: number): void => {
132184
this.collectCheckResult(
133185
result !== 0,

tests/e2e/run.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,17 @@ runEndToEndTest("compilationFailed", "", (error, stdout, stderr) => {
5555
assert(error.code === 2);
5656
});
5757

58+
runEndToEndTest("isolated-cli", "--isolated false", (error, stdout, stderr) => {});
59+
runEndToEndTest("isolated-false", "", (error, stdout, stderr) => {});
60+
runEndToEndTest("isolated-true", "", (error, stdout, stderr) => {});
61+
5862
runEndToEndTest("printLogInFailedInfo", "", (error, stdout, stderr) => {
5963
assert(error.code === 1);
6064
});
6165

62-
runEndToEndTest("isolated-true", "", (error, stdout, stderr) => {});
63-
runEndToEndTest("isolated-false", "", (error, stdout, stderr) => {});
64-
runEndToEndTest("isolated-cli", "--isolated false", (error, stdout, stderr) => {});
66+
runEndToEndTest("setup-teardown", "", (error, stdout, stderr) => {
67+
assert(error.code === 1);
68+
});
6569

6670
runEndToEndTest(
6771
"testFiles",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import path from "node:path";
2+
3+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
4+
5+
/**
6+
* @type {import("../../../config.d.ts").Config}
7+
*/
8+
export default {
9+
include: [__dirname],
10+
temp: path.join(__dirname, "tmp"),
11+
output: path.join(__dirname, "tmp"),
12+
mode: [],
13+
isolated: true,
14+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { test, expect, describe, beforeEach } from "../../../assembly";
2+
3+
let setup0 = 0;
4+
let setup1 = 0;
5+
describe("setup", () => {
6+
beforeEach(() => {
7+
setup0 = 10;
8+
setup1 = 20;
9+
});
10+
describe("nested", () => {
11+
test("1st", () => {
12+
expect(setup0).equal(10);
13+
expect(setup1).equal(20);
14+
setup0 = 100;
15+
setup1 = 200;
16+
});
17+
test("2nd", () => {
18+
expect(setup0).equal(10);
19+
expect(setup1).equal(20);
20+
setup0 = 100;
21+
setup1 = 200;
22+
});
23+
});
24+
test("3nd", () => {
25+
expect(setup0).equal(10);
26+
expect(setup1).equal(20);
27+
});
28+
});

0 commit comments

Comments
 (0)