diff --git a/packages/vitest/src/node/reporters/junit.ts b/packages/vitest/src/node/reporters/junit.ts index fbe13767645a..e15e22ea2155 100644 --- a/packages/vitest/src/node/reporters/junit.ts +++ b/packages/vitest/src/node/reporters/junit.ts @@ -4,6 +4,7 @@ import { dirname, relative, resolve } from 'pathe' import type { Task } from '@vitest/runner' import type { ErrorWithDiff } from '@vitest/utils' +import { getSuites } from '@vitest/runner/utils' import type { Vitest } from '../../node' import type { Reporter } from '../../types/reporter' import { parseErrorStacktrace } from '../../utils/source-map' @@ -219,6 +220,15 @@ export class JUnitReporter implements Reporter { skipped: 0, }) + // inject failed suites to surface errors during beforeAll/afterAll + const suites = getSuites(file) + for (const suite of suites) { + if (suite.result?.errors) { + tasks.push(suite) + stats.failures += 1 + } + } + // If there are no tests, but the file failed to load, we still want to report it as a failure if (tasks.length === 0 && file.result?.state === 'fail') { stats.failures = 1 diff --git a/test/reporters/fixtures/suite-hook-failure/basic.test.ts b/test/reporters/fixtures/suite-hook-failure/basic.test.ts new file mode 100644 index 000000000000..6ea7da43d681 --- /dev/null +++ b/test/reporters/fixtures/suite-hook-failure/basic.test.ts @@ -0,0 +1,26 @@ +import { + afterAll, + beforeAll, + describe, + it, +} from 'vitest'; + +describe('suite with beforeAll', () => { + beforeAll(() => { + throw new Error('beforeAll error'); + }); + + it('ok 1', () => {}); + it('ok 2', () => {}); + it.skip('skip 1', () => {}); +}); + +describe('suite with afterAll', () => { + afterAll(() => { + throw new Error('afterAll error'); + }); + + it('ok 1', () => {}); + it('ok 2', () => {}); + it.skip('skip 1', () => {}); +}); diff --git a/test/reporters/fixtures/suite-hook-failure/vitest.config.ts b/test/reporters/fixtures/suite-hook-failure/vitest.config.ts new file mode 100644 index 000000000000..abed6b2116e1 --- /dev/null +++ b/test/reporters/fixtures/suite-hook-failure/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) diff --git a/test/reporters/tests/__snapshots__/junit.test.ts.snap b/test/reporters/tests/__snapshots__/junit.test.ts.snap new file mode 100644 index 000000000000..a6c51a996401 --- /dev/null +++ b/test/reporters/tests/__snapshots__/junit.test.ts.snap @@ -0,0 +1,36 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`emits when beforeAll/afterAll failed 1`] = ` +" + + + + + + + + + + + + + + + + + + +Error: beforeAll error + ❯ basic.test.ts:10:11 + + + + +Error: afterAll error + ❯ basic.test.ts:20:11 + + + + +" +`; diff --git a/test/reporters/tests/junit.test.ts b/test/reporters/tests/junit.test.ts index f869ccd206e7..cb8c6ce18ec9 100644 --- a/test/reporters/tests/junit.test.ts +++ b/test/reporters/tests/junit.test.ts @@ -52,3 +52,10 @@ test('emits if a test has a syntax error', async () => { expect(xml).toContain('') expect(xml).toContain(' when beforeAll/afterAll failed', async () => { + let { stdout } = await runVitest({ reporters: 'junit', root: './fixtures/suite-hook-failure' }) + // reduct non-deterministic output + stdout = stdout.replaceAll(/(timestamp|hostname|time)=".*?"/g, '$1="..."') + expect(stdout).toMatchSnapshot() +})