Skip to content

Commit ad6ed0c

Browse files
authored
test: add retry assertion test case (#64)
* test: add retry assertion test case - Add test case to verify retry mechanism in assertions\n- Create plugin-retry.ts mock for testing retry functionality\n- Move StepName type to domain.ts for better organization * refactor: rename StepName to TestCasePhases for clarity * chore: bump package versions to 0.20.0
1 parent 12fcbb9 commit ad6ed0c

File tree

13 files changed

+120
-60
lines changed

13 files changed

+120
-60
lines changed

assert/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/assert",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "Extra assert library",
55
"type": "module",
66
"repository": {

dtc-aws-plugin/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/dtc-aws-plugin",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "AWS plugin for Declarative TestCases",
55
"repository": {
66
"type": "git",
@@ -22,10 +22,10 @@
2222
"@aws-sdk/client-sns": "^3.645.0",
2323
"@aws-sdk/lib-dynamodb": "^3.645.0",
2424
"@aws-sdk/util-dynamodb": "^3.645.0",
25-
"@cgauge/assert": "^0.19.0",
26-
"@cgauge/dtc": "^0.19.0",
27-
"@cgauge/nock-aws": "^0.19.0",
28-
"@cgauge/type-guard": "^0.19.0"
25+
"@cgauge/assert": "^0.20.0",
26+
"@cgauge/dtc": "^0.20.0",
27+
"@cgauge/nock-aws": "^0.20.0",
28+
"@cgauge/type-guard": "^0.20.0"
2929
},
3030
"scripts": {
3131
"build": "tsc --build --verbose",

dtc-graphql-plugin/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/dtc-graphql-plugin",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "GraphQl plugin for Declarative TestCases",
55
"repository": {
66
"type": "git",
@@ -15,9 +15,9 @@
1515
"author": "Salam Suleymanov",
1616
"license": "LGPL-3.0-or-later",
1717
"dependencies": {
18-
"@cgauge/assert": "^0.19.0",
19-
"@cgauge/dtc": "^0.19.0",
20-
"@cgauge/type-guard": "^0.19.0",
18+
"@cgauge/assert": "^0.20.0",
19+
"@cgauge/dtc": "^0.20.0",
20+
"@cgauge/type-guard": "^0.20.0",
2121
"graphql-request": "^7.1.2"
2222
},
2323
"scripts": {

dtc-mysql-plugin/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/dtc-mysql-plugin",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "MySQL plugin for Declarative TestCases",
55
"repository": {
66
"type": "git",
@@ -15,9 +15,9 @@
1515
"author": "Abdala Cerqueira",
1616
"license": "LGPL-3.0-or-later",
1717
"dependencies": {
18-
"@cgauge/assert": "^0.19.0",
19-
"@cgauge/dtc": "^0.19.0",
20-
"@cgauge/type-guard": "^0.19.0",
18+
"@cgauge/assert": "^0.20.0",
19+
"@cgauge/dtc": "^0.20.0",
20+
"@cgauge/type-guard": "^0.20.0",
2121
"mysql2": "^3.11.0",
2222
"node-sql-parser": "^5.1.0",
2323
"type-assurance": "^1.5.1"

dtc-playwright-plugin/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/dtc-playwright-plugin",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "Playwright plugin for Declarative TestCases",
55
"repository": {
66
"type": "git",
@@ -15,9 +15,9 @@
1515
"author": "Abdala Cerqueira",
1616
"license": "LGPL-3.0-or-later",
1717
"dependencies": {
18-
"@cgauge/assert": "^0.19.0",
19-
"@cgauge/dtc": "^0.19.0",
20-
"@cgauge/type-guard": "^0.19.0",
18+
"@cgauge/assert": "^0.20.0",
19+
"@cgauge/dtc": "^0.20.0",
20+
"@cgauge/type-guard": "^0.20.0",
2121
"@playwright/test": "^1.47.0"
2222
},
2323
"scripts": {

dtc/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cgauge/dtc",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "Declarative TestCases",
55
"repository": {
66
"type": "git",
@@ -18,8 +18,8 @@
1818
"author": "Abdala Cerqueira",
1919
"license": "LGPL-3.0-or-later",
2020
"dependencies": {
21-
"@cgauge/assert": "^0.19.0",
22-
"@cgauge/type-guard": "^0.19.0",
21+
"@cgauge/assert": "^0.20.0",
22+
"@cgauge/type-guard": "^0.20.0",
2323
"cleye": "^1.3.2",
2424
"nock": "^14.0.1"
2525
},

dtc/src/domain.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export type Runner = (
99
config?: string,
1010
) => Promise<void>
1111

12+
export type TestCasePhases = 'arrange' | 'act' | 'assert' | 'clean'
13+
1214
const GenericAttributes = record(
1315
String,
1416
union(String, Number, Boolean, record(String, unknown), [record(String, unknown)]),

dtc/src/index.ts

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {TestCase, TestCaseExecution} from './domain'
1+
import type {TestCase, TestCaseExecution, TestCasePhases} from './domain'
22
import {debug, retry} from './utils.js'
33
import {dirname} from 'node:path'
44
import test from 'node:test'
@@ -22,8 +22,8 @@ export const defaultPlugins = [
2222
'./plugins/http-mock-plugin.js',
2323
]
2424

25-
const preparePluginFunction =
26-
(plugins: any[], basePath: string, testRunnerArgs?: unknown) => async (functionName: string, data: unknown) => {
25+
const createPluginExecutor = (plugins: any[], basePath: string, testRunnerArgs?: unknown) => {
26+
return async (functionName: string, data: unknown) => {
2727
if (!data) {
2828
return
2929
}
@@ -41,21 +41,64 @@ const preparePluginFunction =
4141
throw new Error(`No actions (${functionName}) executed`)
4242
}
4343
}
44+
}
45+
46+
const createPhaseExecutor = (
47+
executePluginFunction: (functionName: string, data: unknown) => Promise<void>,
48+
testCaseExecution: TestCaseExecution,
49+
) => {
50+
const allowedLayerPhases = ['arrange', 'clean']
51+
52+
const executePhase = async (phase: TestCasePhases, testCase: TestCase) => {
53+
if (!testCase[phase]) {
54+
return
55+
}
56+
57+
if (phase === 'assert') {
58+
await retry(() => executePluginFunction(phase, testCase[phase]), testCase.retry, testCase.delay)
59+
} else {
60+
await executePluginFunction(phase, testCase[phase])
61+
}
62+
}
63+
64+
const executeLayerPhase = async (phase: TestCasePhases, layers: TestCase[]) => {
65+
await Promise.all(
66+
layers
67+
.filter((layer) => layer[phase])
68+
.map((layer) => executePhase(phase, layer))
69+
)
70+
}
71+
72+
const execute = async (phase: TestCasePhases) => {
73+
if (testCaseExecution.resolvedLayers && allowedLayerPhases.includes(phase)) {
74+
await executeLayerPhase(phase, testCaseExecution.resolvedLayers)
75+
}
76+
77+
await executePhase(phase, testCaseExecution.testCase)
78+
}
79+
80+
return { execute }
81+
}
4482

45-
const runSafe = (
83+
const createTestCaseExecutor = async (
4684
testCaseExecution: TestCaseExecution,
47-
executePluginFunction: (functionName: string, data: unknown) => Promise<void>
85+
plugins: string[],
86+
testRunnerArgs?: unknown,
4887
) => {
88+
const basePath = dirname(testCaseExecution.filePath)
89+
const loadedPlugins = await Promise.all(plugins.map((plugin) => import(plugin)))
90+
const executePluginFunction = createPluginExecutor(loadedPlugins, basePath, testRunnerArgs)
91+
const phaseExecutor = createPhaseExecutor(executePluginFunction, testCaseExecution)
4992
let errors: Error[] = []
5093

51-
const step = async (step: string, testCase: TestCase) => {
94+
const execute = async (phase: TestCasePhases) => {
5295
try {
53-
if (!testCase[step] || (errors.length > 0 && step !== 'clean')) {
96+
if (errors.length > 0 && phase !== 'clean') {
5497
return
5598
}
56-
await executePluginFunction(step, testCase[step])
99+
await phaseExecutor.execute(phase)
57100
} catch (err: any) {
58-
err.name = `${step.charAt(0).toUpperCase() + step.slice(1)}Error`
101+
err.name = `${phase.charAt(0).toUpperCase() + phase.slice(1)}Error`
59102
errors.push(err)
60103
debug(`${err.name}: ${err.message} \n${err.stack}`)
61104
}
@@ -70,10 +113,9 @@ const runSafe = (
70113
}
71114
}
72115

73-
return { step, throwIfError }
116+
return { execute, throwIfError }
74117
}
75118

76-
77119
export const executeTestCase = async (
78120
testCaseExecution: TestCaseExecution,
79121
plugins: string[],
@@ -83,32 +125,12 @@ export const executeTestCase = async (
83125
debug(`TestRunnerArgs: ${JSON.stringify(testRunnerArgs, null, 2)}`)
84126
debug(`Plugins: ${JSON.stringify(plugins, null, 2)}`)
85127

86-
const basePath = dirname(testCaseExecution.filePath)
87-
88-
const loadedPlugins = await Promise.all(plugins.map((plugin) => import(plugin)))
89-
const executePluginFunction = preparePluginFunction(loadedPlugins, basePath, testRunnerArgs)
90-
const testCase = testCaseExecution.testCase
91-
const { step, throwIfError } = runSafe(testCaseExecution, executePluginFunction)
92-
93-
if (testCaseExecution.resolvedLayers) {
94-
await Promise.all(
95-
testCaseExecution.resolvedLayers.filter((v) => v.arrange).map((v) => step('arrange', v)),
96-
)
97-
}
98-
99-
await step('arrange', testCase)
100-
101-
await step('act', testCase)
102-
103-
await retry(() => step('assert', testCase), testCase.retry, testCase.delay)
128+
const executor = await createTestCaseExecutor(testCaseExecution, plugins, testRunnerArgs)
129+
const phases: TestCasePhases[] = ['arrange', 'act', 'assert', 'clean']
104130

105-
await step('clean', testCase)
106-
107-
if (testCaseExecution.resolvedLayers) {
108-
await Promise.all(
109-
testCaseExecution.resolvedLayers.filter((v) => v.clean).map((v) => step('clean', v)),
110-
)
131+
for (const phase of phases) {
132+
await executor.execute(phase)
111133
}
112134

113-
throwIfError()
135+
executor.throwIfError()
114136
}

dtc/test/fixtures/plugin-retry.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {mock} from 'node:test'
2+
3+
export const arrange = mock.fn(() => true)
4+
export const act = mock.fn(() => true)
5+
export const assert = mock.fn((data) => {
6+
if (assert.mock.callCount() < 2) {
7+
throw new Error('Failing assertion')
8+
}
9+
return true
10+
})
11+
export const clean = mock.fn(() => true)

dtc/test/index.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ import {
2929
assert as assertCleanup,
3030
clean as cleanCleanup,
3131
} from './fixtures/plugin-cleanup.ts'
32+
import {
33+
arrange as arrangeRetry,
34+
act as actRetry,
35+
assert as assertRetry,
36+
clean as cleanRetry,
37+
} from './fixtures/plugin-retry.ts'
3238

3339
test('It runs plugins methods', async () => {
3440
await executeTestCase(
@@ -153,3 +159,22 @@ test('It runs cleanup even if arrange, act or assert fails', async () => {
153159
nodeAssert.equal(assertCleanup.mock.callCount(), 0)
154160
nodeAssert.equal(cleanCleanup.mock.callCount(), 5)
155161
})
162+
163+
test('It retries assertion when it fails', async () => {
164+
await executeTestCase(
165+
{
166+
testCase: {
167+
name: 'Test with retry',
168+
act: {},
169+
assert: {},
170+
retry: 2,
171+
delay: 0.1
172+
},
173+
filePath: './filePath.js',
174+
},
175+
['../test/fixtures/plugin-retry.ts'],
176+
)
177+
178+
nodeAssert.equal(actRetry.mock.callCount(), 1)
179+
nodeAssert.equal(assertRetry.mock.callCount(), 3)
180+
})

0 commit comments

Comments
 (0)