[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix(runner): the fixture of `test.extend` should be init once time in all test (#4168)

authored by

Dunqing and committed by
GitHub
(Sep 27, 2023, 9:39 AM +0200) a5979ea0 4e941208

+214 -39
+58 -12
packages/runner/src/fixture.ts
··· 44 44 return context 45 45 } 46 46 47 + const fixtureValueMap = new Map<FixtureItem, any>() 48 + const fixtureCleanupFnMap = new Map<string, Array<() => void | Promise<void>>>() 49 + 50 + export async function callFixtureCleanup(id: string) { 51 + const cleanupFnArray = fixtureCleanupFnMap.get(id) 52 + if (!cleanupFnArray) 53 + return 54 + 55 + for (const cleanup of cleanupFnArray.reverse()) 56 + await cleanup() 57 + 58 + fixtureCleanupFnMap.delete(id) 59 + } 60 + 47 61 export function withFixtures(fn: Function, testContext?: TestContext) { 48 62 return (hookContext?: TestContext) => { 49 63 const context: TestContext & { [key: string]: any } | undefined = hookContext || testContext 50 64 51 65 if (!context) 52 66 return fn({}) 67 + 68 + let cleanupFnArray = fixtureCleanupFnMap.get(context.task.suite.id)! 69 + if (!cleanupFnArray) { 70 + cleanupFnArray = [] 71 + fixtureCleanupFnMap.set(context.task.suite.id, cleanupFnArray) 72 + } 53 73 54 74 const fixtures = getFixture(context) 55 75 if (!fixtures?.length) ··· 63 83 const pendingFixtures = resolveDeps(usedFixtures) 64 84 let cursor = 0 65 85 66 - async function use(fixtureValue: any) { 67 - const { prop } = pendingFixtures[cursor++] 68 - context![prop] = fixtureValue 86 + return new Promise((resolve, reject) => { 87 + async function use(fixtureValue: any) { 88 + const fixture = pendingFixtures[cursor++] 89 + context![fixture.prop] = fixtureValue 69 90 70 - if (cursor < pendingFixtures.length) 71 - await next() 72 - else await fn(context) 73 - } 91 + if (!fixtureValueMap.has(fixture)) { 92 + fixtureValueMap.set(fixture, fixtureValue) 93 + cleanupFnArray.unshift(() => { 94 + fixtureValueMap.delete(fixture) 95 + }) 96 + } 74 97 75 - async function next() { 76 - const { value } = pendingFixtures[cursor] 77 - typeof value === 'function' ? await value(context, use) : await use(value) 78 - } 98 + if (cursor < pendingFixtures.length) { 99 + await next() 100 + } 101 + else { 102 + // When all fixtures setup, call the test function 103 + try { 104 + resolve(await fn(context)) 105 + } 106 + catch (err) { 107 + reject(err) 108 + } 109 + return new Promise<void>((resolve) => { 110 + cleanupFnArray.push(resolve) 111 + }) 112 + } 113 + } 79 114 80 - return next() 115 + async function next() { 116 + const fixture = pendingFixtures[cursor] 117 + const { isFn, value } = fixture 118 + if (fixtureValueMap.has(fixture)) 119 + return use(fixtureValueMap.get(fixture)) 120 + else 121 + return isFn ? value(context, use) : use(value) 122 + } 123 + 124 + const setupFixturePromise = next() 125 + cleanupFnArray.unshift(() => setupFixturePromise) 126 + }) 81 127 } 82 128 } 83 129
+2
packages/runner/src/run.ts
··· 10 10 import { setCurrentTest } from './test-state' 11 11 import { hasFailed, hasTests } from './utils/tasks' 12 12 import { PendingError } from './errors' 13 + import { callFixtureCleanup } from './fixture' 13 14 14 15 const now = Date.now 15 16 ··· 321 322 } 322 323 323 324 try { 325 + await callFixtureCleanup(suite.id) 324 326 await callSuiteHook(suite, suite, 'afterAll', runner, [suite]) 325 327 await callCleanupHooks(beforeAllCleanups) 326 328 }
+154 -27
test/core/test/test-extend.test.ts
··· 1 1 /* eslint-disable prefer-rest-params */ 2 2 /* eslint-disable no-empty-pattern */ 3 - import { describe, expect, expectTypeOf, test, vi } from 'vitest' 3 + import { afterAll, afterEach, beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest' 4 4 5 5 interface Fixtures { 6 6 todoList: number[] ··· 38 38 }) 39 39 40 40 describe('test.extend()', () => { 41 - myTest('todoList and doneList', ({ todoList, doneList, archiveList }) => { 42 - expect(todoFn).toBeCalledTimes(1) 43 - expect(doneFn).toBeCalledTimes(1) 41 + describe('basic', () => { 42 + myTest('todoList and doneList', ({ todoList, doneList, archiveList }) => { 43 + expect(todoFn).toBeCalledTimes(1) 44 + expect(doneFn).toBeCalledTimes(1) 44 45 45 - expectTypeOf(todoList).toEqualTypeOf<number[]>() 46 - expectTypeOf(doneList).toEqualTypeOf<number[]>() 47 - expectTypeOf(doneList).toEqualTypeOf<number[]>() 46 + expectTypeOf(todoList).toEqualTypeOf<number[]>() 47 + expectTypeOf(doneList).toEqualTypeOf<number[]>() 48 + expectTypeOf(doneList).toEqualTypeOf<number[]>() 48 49 49 - expect(todoList).toEqual([1, 2, 3]) 50 - expect(doneList).toEqual([]) 51 - expect(archiveList).toEqual([]) 50 + expect(todoList).toEqual([1, 2, 3]) 51 + expect(doneList).toEqual([]) 52 + expect(archiveList).toEqual([]) 52 53 53 - doneList.push(todoList.shift()!) 54 - expect(todoList).toEqual([2, 3]) 55 - expect(doneList).toEqual([1]) 54 + doneList.push(todoList.shift()!) 55 + expect(todoList).toEqual([2, 3]) 56 + expect(doneList).toEqual([1]) 56 57 57 - doneList.push(todoList.shift()!) 58 - expect(todoList).toEqual([3]) 59 - expect(doneList).toEqual([1, 2]) 58 + doneList.push(todoList.shift()!) 59 + expect(todoList).toEqual([3]) 60 + expect(doneList).toEqual([1, 2]) 60 61 61 - archiveList.push(todoList.shift()!) 62 - expect(todoList).toEqual([]) 63 - expect(archiveList).toEqual([3]) 62 + archiveList.push(todoList.shift()!) 63 + expect(todoList).toEqual([]) 64 + expect(archiveList).toEqual([3]) 64 65 65 - archiveList.pop() 66 + archiveList.pop() 67 + }) 66 68 }) 67 - 68 - myTest('should called cleanup functions', ({ todoList, doneList, archiveList }) => { 69 - expect(todoList).toEqual([1, 2, 3]) 70 - expect(doneList).toEqual([]) 71 - expect(archiveList).toEqual([]) 72 - }) 73 - 74 69 describe('smartly init fixtures', () => { 75 70 myTest('should not init any fixtures', function () { 76 71 expect(todoFn).not.toBeCalled() ··· 148 143 expect(todos).toEqual([1, 2, 3]) 149 144 expect(done).toEqual([]) 150 145 expect(archive).toEqual([]) 146 + }) 147 + }) 148 + 149 + describe('fixture call times', () => { 150 + const apiFn = vi.fn(() => true) 151 + const serviceFn = vi.fn(() => true) 152 + const teardownFn = vi.fn() 153 + 154 + interface APIFixture { 155 + api: boolean 156 + service: boolean 157 + } 158 + 159 + const testAPI = test.extend<APIFixture>({ 160 + api: async ({}, use) => { 161 + await use(apiFn()) 162 + apiFn.mockClear() 163 + teardownFn() 164 + }, 165 + service: async ({}, use) => { 166 + await use(serviceFn()) 167 + serviceFn.mockClear() 168 + teardownFn() 169 + }, 170 + }) 171 + 172 + beforeEach<APIFixture>(({ api, service }) => { 173 + expect(api).toBe(true) 174 + expect(service).toBe(true) 175 + }) 176 + 177 + testAPI('Should init1 time', ({ api }) => { 178 + expect(api).toBe(true) 179 + expect(apiFn).toBeCalledTimes(1) 180 + }) 181 + 182 + testAPI('Should init 1 time has multiple fixture', ({ api, service }) => { 183 + expect(api).toBe(true) 184 + expect(service).toBe(true) 185 + expect(serviceFn).toBeCalledTimes(1) 186 + expect(apiFn).toBeCalledTimes(1) 187 + }) 188 + 189 + afterEach<APIFixture>(({ api, service }) => { 190 + expect(api).toBe(true) 191 + expect(service).toBe(true) 192 + expect(apiFn).toBeCalledTimes(1) 193 + expect(serviceFn).toBeCalledTimes(1) 194 + }) 195 + 196 + afterAll(() => { 197 + expect(serviceFn).toBeCalledTimes(0) 198 + expect(apiFn).toBeCalledTimes(0) 199 + expect(teardownFn).toBeCalledTimes(2) 200 + }) 201 + }) 202 + 203 + describe('fixture in nested describe', () => { 204 + interface Fixture { 205 + foo: number 206 + bar: number 207 + } 208 + 209 + const fooFn = vi.fn(() => 0) 210 + const fooCleanup = vi.fn() 211 + 212 + const barFn = vi.fn(() => 0) 213 + const barCleanup = vi.fn() 214 + 215 + const nestedTest = test.extend<Fixture>({ 216 + async foo({}, use) { 217 + await use(fooFn()) 218 + fooCleanup() 219 + }, 220 + async bar({}, use) { 221 + await use(barFn()) 222 + barCleanup() 223 + }, 224 + }) 225 + 226 + beforeEach<Fixture>(({ foo }) => { 227 + expect(foo).toBe(0) 228 + }) 229 + 230 + nestedTest('should only initialize foo', ({ foo }) => { 231 + expect(foo).toBe(0) 232 + expect(fooFn).toBeCalledTimes(1) 233 + expect(barFn).toBeCalledTimes(0) 234 + }) 235 + 236 + describe('level 2, using both foo and bar together', () => { 237 + beforeEach<Fixture>(({ foo, bar }) => { 238 + expect(foo).toBe(0) 239 + expect(bar).toBe(0) 240 + }) 241 + 242 + nestedTest('should only initialize bar', ({ foo, bar }) => { 243 + expect(foo).toBe(0) 244 + expect(bar).toBe(0) 245 + expect(fooFn).toBeCalledTimes(1) 246 + expect(barFn).toBeCalledTimes(1) 247 + }) 248 + 249 + afterEach<Fixture>(({ foo, bar }) => { 250 + expect(foo).toBe(0) 251 + expect(bar).toBe(0) 252 + }) 253 + 254 + afterAll(() => { 255 + // foo setup in outside describe 256 + // cleanup also called in outside describe 257 + expect(fooCleanup).toHaveBeenCalledTimes(0) 258 + // bar setup in inside describe 259 + // cleanup also called in inside describe 260 + expect(barCleanup).toHaveBeenCalledTimes(1) 261 + }) 262 + }) 263 + 264 + nestedTest('level 2 will not call foo cleanup', ({ foo }) => { 265 + expect(foo).toBe(0) 266 + expect(fooFn).toBeCalledTimes(1) 267 + }) 268 + 269 + afterEach<Fixture>(({ foo }) => { 270 + expect(foo).toBe(0) 271 + }) 272 + 273 + afterAll(() => { 274 + // foo setup in this describe 275 + // cleanup also called in this describe 276 + expect(fooCleanup).toHaveBeenCalledTimes(1) 277 + expect(barCleanup).toHaveBeenCalledTimes(1) 151 278 }) 152 279 }) 153 280 })