Skip to content

Commit 5ceb172

Browse files
authored
Merge pull request #59 from stbestichhh/feature/timeout
Feature/timeout
2 parents 177be31 + 662ad79 commit 5ceb172

File tree

5 files changed

+63
-14
lines changed

5 files changed

+63
-14
lines changed

bin/runner/test.runner.ts

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Cli } from '../cli';
1414
import { Config } from '../config';
1515
import { MockRegistry } from '../../lib';
1616
import { isTable } from '../../utils';
17+
import { TimeoutException } from '../../lib/exceptions';
1718

1819
export class TestRunner {
1920
private static isAllPassed: boolean = true;
@@ -25,10 +26,14 @@ export class TestRunner {
2526
await this.runTestSuite(testName, target);
2627
}
2728
} finally {
28-
if (!this.isAllPassed && !Cli.getOptions('watch')) {
29-
exit(1);
30-
}
3129
TestRegistry.clear();
30+
process.nextTick(() => {
31+
if (!this.isAllPassed && !Cli.getOptions('watch')) {
32+
exit(1);
33+
} else {
34+
exit(0);
35+
}
36+
});
3237
}
3338
}
3439

@@ -51,14 +56,15 @@ export class TestRunner {
5156

5257
await this.runLifecycleMethods(testSuite, beforeAll, 'beforeAll');
5358

54-
for (const { methodName, caseDescription } of testCases) {
59+
for (const { methodName, caseDescription, timeout } of testCases) {
5560
await this.runLifecycleMethods(testSuite, beforeEach, 'beforeEach');
5661
await this.runTestCase(
5762
testSuite,
5863
methodName,
5964
caseDescription,
6065
dataSetsArray,
6166
dataTableArray,
67+
timeout,
6268
);
6369
await this.runLifecycleMethods(testSuite, afterEach, 'afterEach');
6470
this.clearMocks('afterEach');
@@ -100,6 +106,7 @@ export class TestRunner {
100106
caseDescription: string,
101107
dataSetsArray: IDataSet[],
102108
dataTableArray: IDataTableArray[],
109+
timeout: number,
103110
) {
104111
try {
105112
const { dataTable, dataSets } = this.getCaseData(
@@ -113,9 +120,15 @@ export class TestRunner {
113120
testSuiteInstance,
114121
methodName,
115122
dataTable,
123+
timeout,
116124
);
117125
} else {
118-
await this.runTestCaseWithData(testSuiteInstance, methodName, dataSets);
126+
await this.runTestCaseWithData(
127+
testSuiteInstance,
128+
methodName,
129+
dataSets,
130+
timeout,
131+
);
119132
}
120133

121134
this.logTestResult(caseDescription || methodName, 'PASSED', 'grey');
@@ -129,28 +142,49 @@ export class TestRunner {
129142
testSuiteInstance: any,
130143
methodName: string,
131144
dataArray: IDataSet[][] | IDataTable[],
145+
timeout: number,
132146
) {
133147
if (dataArray.length <= 0) {
134-
return await this.getTestResult(testSuiteInstance, methodName, []);
148+
return await this.getTestResult(
149+
testSuiteInstance,
150+
methodName,
151+
[],
152+
timeout,
153+
);
135154
}
136155

137156
for (const data of dataArray) {
138-
await this.getTestResult(testSuiteInstance, methodName, data);
157+
await this.getTestResult(testSuiteInstance, methodName, data, timeout);
139158
}
140159
}
141160

142161
private static async getTestResult(
143162
testSuiteInstance: any,
144163
methodName: string,
145164
data: IDataSet[] | IDataTable,
165+
timeout: number = 5000,
146166
) {
147-
const result = isTable(data)
148-
? testSuiteInstance[methodName](...data.inputs, data.expected)
149-
: testSuiteInstance[methodName](...data);
167+
const testPromise = new Promise<void>((resolve, reject) => {
168+
const result = isTable(data)
169+
? testSuiteInstance[methodName](...data.inputs, data.expected)
170+
: testSuiteInstance[methodName](...data);
150171

151-
if (result instanceof Promise) {
152-
await result;
153-
}
172+
if (result instanceof Promise) {
173+
result.then(resolve).catch(reject);
174+
} else {
175+
resolve();
176+
}
177+
});
178+
179+
const timeoutPromise = new Promise((_, reject) => {
180+
setTimeout(
181+
reject,
182+
timeout,
183+
new TimeoutException(`Test timed out in ${timeout}ms`),
184+
);
185+
});
186+
187+
await Promise.race([testPromise, timeoutPromise]);
154188
}
155189

156190
private static getCaseData(

lib/decorators/case.decorator.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export function Case(caseDescription?: string): MethodDecorator {
1+
export function Case(
2+
options?: string | { description?: string; timeout?: number },
3+
): MethodDecorator {
24
return function (
35
target: object,
46
key: string | symbol,
@@ -10,9 +12,14 @@ export function Case(caseDescription?: string): MethodDecorator {
1012
constructor.testCases = [];
1113
}
1214

15+
const caseDescription =
16+
typeof options === 'string' ? options : options?.description;
17+
const timeout = typeof options !== 'string' ? options?.timeout : undefined;
18+
1319
constructor.testCases.push({
1420
methodName: key,
1521
caseDescription,
22+
timeout,
1623
});
1724

1825
return descriptor;

lib/exceptions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './assertionException';
22
export * from './loadFileException';
33
export * from './mockingException';
44
export * from './configExceptions';
5+
export * from './timeoutException';

lib/exceptions/timeoutException.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class TimeoutException extends Error {
2+
constructor(public readonly message: string) {
3+
super(message);
4+
this.name = 'TimeoutError';
5+
}
6+
}

lib/interfaces/ITestCase.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface ITestCase {
22
readonly methodName: string;
33
readonly caseDescription: string;
4+
readonly timeout: number;
45
}

0 commit comments

Comments
 (0)