Skip to content

Commit

Permalink
fix(runner): correctly initialize suite in task
Browse files Browse the repository at this point in the history
this adds a test with a custom runner that allows you to wait for other tests, even when they weren't scheduled. It requires `suite` to be defined at context extension time.
  • Loading branch information
wmertens committed Jan 29, 2025
1 parent 9acc13f commit bdd9a7a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/runner/src/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ function createSuiteCollector(
const task: Test = {
id: '',
name,
suite: undefined!,
suite,
each: options.each,
fails: options.fails,
context: undefined!,
Expand Down
53 changes: 53 additions & 0 deletions test/cli/fixtures/custom-runner/custom-runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {describe, expect, test, type TestAPI} from 'vitest'

const myTest = test as TestAPI<{result: (name: string) => Promise<unknown>}>

describe('await multiple prior test result', () => {
myTest('first', async () => {
expect(1).toBe(1)
return 1
})

myTest('second', async () => {
expect(1).toBe(1)
return 2
})

myTest('third', async () => {
expect(1).toBe(1)
return 3
})

myTest.only('with await', async ({result}) => {
const third = await result('await multiple prior test result | third')
expect(third).toBe(3)

const second = await result('await multiple prior test result | second')
expect(second).toBe(2)

const first = await result('await multiple prior test result | first')
expect(first).toEqual(1)
})
})

myTest.only('supports passing options', {repeats: 2}, context => {
expect(context.task.repeats).toBe(2)
})

describe.only('extendable', () => {
const newTest = myTest.extend({
foo: 'bar',
// eslint-disable-next-line no-empty-pattern
baz: async ({}, use) => {
await use('qux')
},
})
newTest('check non func', ({foo}) => {
expect(foo).toBe('bar')
return 'bar'
})
newTest('check func', async ({baz, result}) => {
expect(baz).toBe('qux')
await expect(result('extendable | check non func')).resolves.toBe('bar')
})
})
60 changes: 60 additions & 0 deletions test/cli/fixtures/custom-runner/test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Task, TestContext } from '@vitest/runner'
import { getFn } from '@vitest/runner'
import { VitestTestRunner } from 'vitest/runners'

/** This adds `result` to the test context, which allows for awaiting prior test results,
* enabling sharing of expensive results between tests while enforcing a correct ordering of tests. */
class CustomTestRunner extends VitestTestRunner {
constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {
super(config)
}
tasks: Record<string, Task> = {}
_results: Record<string, unknown> = {}

result = (prop: string) => {
if (!(prop in this._results)) {
const task = this.tasks[prop as string]
if (!task) {
throw new Error(
`Task ${String(prop)} not found. Known tests: ${Object.keys(
this.tasks
)
.map(name => JSON.stringify(name))
.join(', ')}`
)
}
// this fills in the results for the task
this.runTask(task)
}
return this._results[prop]
}

getName(test: Task) {
const name = test.suite?.name
? `${test.suite.name} | ${test.name}`
: test.name
return name
}

runTask(test: Task) {
const name = this.getName(test)
const fn = getFn(test)

const results = fn()
this._results[name] = results
return results
}

extendTaskContext(context: TestContext) {
super.extendTaskContext(context)
// we need to store the task so we can run it when the results are requested
const {task} = context
const testName = this.getName(task)
this.tasks[testName] = task
;(context as any).result = this.result

return context
}
}

export default CustomTestRunner
7 changes: 7 additions & 0 deletions test/cli/fixtures/custom-runner/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
runner: './test-runner.ts',
},
})
16 changes: 16 additions & 0 deletions test/cli/test/custom-runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'

test('can run custom runner with Vitest', async () => {
const vitest = await runVitest({
root: './fixtures/custom-runner',
reporters: [['default', { isTTY: false }]],
allowOnly: true,
})

expect(vitest.stderr).toMatchInlineSnapshot(`""`)

expect(vitest.stdout).toContain('✓ custom-runner.test.ts')
expect(vitest.stdout).toContain('Test Files 1 passed')
expect(vitest.stdout).toContain('Tests 4 passed | 3 skipped')
})

0 comments on commit bdd9a7a

Please sign in to comment.