Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/mocha/asyncWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import recorder from '../recorder.js'
import assertThrown from '../assert/throws.js'
import { ucfirst, isAsyncFunction } from '../utils.js'
import { getInjectedArguments } from './inject.js'
import { fireHook } from './hooks.js'
import { fireHook, BeforeSuiteHook, AfterSuiteHook } from './hooks.js'

const injectHook = function (inject, suite) {
try {
Expand Down Expand Up @@ -232,6 +232,10 @@ export function suiteSetup(suite) {

// Set up error handler for suite setup
recorder.errHandler(err => {
// A helper's `_beforeSuite()` runs through this hook, not through the
// `injected()` wrapper, so nothing here used to emit `hook.failed` and
// reporters listening for it never saw the failure. (#5660)
event.emit(event.hook.failed, new BeforeSuiteHook(suite, err))
doneFn(err)
})

Expand All @@ -254,6 +258,8 @@ export function suiteTeardown(suite) {

// Set up error handler for suite teardown
recorder.errHandler(err => {
// Same for a helper's `_afterSuite()`. (#5660)
event.emit(event.hook.failed, new AfterSuiteHook(suite, err))
doneFn(err)
})

Expand Down
44 changes: 44 additions & 0 deletions test/unit/mocha/asyncWrapper_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,48 @@ describe('AsyncWrapper', () => {
expect(arg, 'done called with no error').to.be.undefined
})
})
describe('helper lifecycle hook failures (#5660)', () => {
beforeEach(() => recorder.start())

// A helper's _beforeSuite()/_afterSuite() is queued on the recorder from an
// event.suite.before/after listener (lib/listener/helpers.js), not through
// the injected() wrapper, so it lands in suiteSetup/suiteTeardown's
// errHandler rather than in the path that fires hook.failed.
function queueFailingHelperHook(evt, message) {
event.dispatcher.on(evt, () => {
recorder.add(`hook MyHelper.${message}()`, () => {
throw new Error(message)
})
recorder.catch()
})
}

it('suiteSetup(): a failing helper _beforeSuite emits hook.failed', async () => {
const failed = sinon.spy()
event.dispatcher.on(event.hook.failed, failed)
queueFailingHelperHook(event.suite.before, '_beforeSuite')

const suite = { title: 'Login', ctx: { test: { title: 'codeceptjs.beforeSuite' } } }
const { arg } = await runHook(suiteSetup(suite), 2000)

expect(arg).to.be.instanceof(Error)
expect(arg.message).to.equal('_beforeSuite')
expect(failed.called, 'hook.failed was emitted').to.be.true
expect(failed.firstCall.args[0].hookName).to.equal('BeforeSuite')
expect(failed.firstCall.args[0].err).to.equal(arg)
})

it('suiteTeardown(): a failing helper _afterSuite emits hook.failed', async () => {
const failed = sinon.spy()
event.dispatcher.on(event.hook.failed, failed)
queueFailingHelperHook(event.suite.after, '_afterSuite')

const suite = { title: 'Login', ctx: { test: { title: 'codeceptjs.afterSuite' } } }
const { arg } = await runHook(suiteTeardown(suite), 2000)

expect(arg).to.be.instanceof(Error)
expect(failed.called, 'hook.failed was emitted').to.be.true
expect(failed.firstCall.args[0].hookName).to.equal('AfterSuite')
})
})
})
Loading