Skip to content

How to skip or fail Tests in Hooks

Use utils.skip() when a test cannot run due to missing preconditions.

  • Required resources cannot be created
  • Prerequisites are not met
  • Test is not applicable for certain conditions
import { beforeEach } from '@thymian/hooks';
export const seed = beforeEach(
'GET /launches/{id} -> 200 (application/json)',
async (request, ctx, utils) => {
const response = await utils.request(
'POST /launches (application/json) -> 201 (application/json)',
{
body: {
missionName: utils.randomString(10),
launchDate: '2026-12-31T00:00:00Z',
rocketType: 'Falcon9',
},
headers: { 'content-type': 'application/json' },
},
);
// Skip if resource creation failed
if (response.statusCode !== 201) {
utils.skip('Cannot create required launch resource');
}
request.pathParameters.id = response.body.id;
},
);

Provide detailed information about why the test was skipped:

if (response.statusCode === 503) {
utils.skip(
`Service unavailable (status ${response.statusCode}). Cannot proceed with test.`,
);
}

Use utils.fail() when a test should explicitly fail due to invalid conditions.

  • Setup produces unexpected results
  • Resources are in an invalid state
  • Critical preconditions are violated
  • Data validation fails
import { beforeEach } from '@thymian/hooks';
export const seed = beforeEach(
'GET /launches/{id} -> 200 (application/json)',
async (request, ctx, utils) => {
const response = await utils.request(
'POST /launches (application/json) -> 201 (application/json)',
{
body: {
missionName: utils.randomString(10),
launchDate: '2026-12-31T00:00:00Z',
rocketType: 'Falcon9',
},
headers: { 'content-type': 'application/json' },
},
);
// Fail if creation returned a status other than the one declared here.
// Branch on the declared status and narrow before touching `body` — the
// type of `response` is a union of every response the operation
// declares, so a status that operation never declares (like `200` for
// an endpoint that only declares `201`) is a compile error, and `body`
// is only the 201 shape once the branch has narrowed to it.
if (response.statusCode !== 201) {
utils.fail(`Expected 201 Created, got ${response.statusCode}`);
} else if (!response.body.id) {
utils.fail('Created launch has no ID field');
} else {
request.pathParameters.id = response.body.id;
}
},
);
utils.skip('Resource unavailable');

Result:

  • Test is marked as skipped
  • Remaining hooks do not execute
  • Test does not count as failure
  • Test report shows skip reason

Output:

GET /launches/123
⊘ Skipped: Resource unavailable
utils.fail('Invalid response structure');

Result:

  • Test is marked as failed
  • Remaining hooks do not execute
  • Test counts as failure in report
  • Test report shows failure reason

Output:

GET /launches/123
✗ Failed: Invalid response structure

Both skip() and fail() throw special errors that are caught by the hook runner:

// These throw errors internally
utils.skip('message'); // Throws SkipError
utils.fail('message'); // Throws FailError
export const seed = beforeEach(
{ path: '/launches/{id}', statusClass: '2XX' },
async (request, ctx, utils) => {
// Only the successful cases need a resource to exist.
},
);

Narrowing the target is almost always better than branching inside the hook: the hook simply never runs where it does not apply, and a filter value that stops existing is a compile error, whereas a branch is just code that quietly stops matching.

For validation in afterEach hooks, consider using utils.assertionFailure() instead of utils.fail():

export const assertShape = afterEach(
'GET /launches/{id} -> 200 (application/json)',
(response, ctx, utils) => {
const body = JSON.parse(response.body ?? '{}');
if (!body.id) {
utils.assertionFailure('Missing ID field', {
expected: 'id field present',
actual: 'id field missing',
});
}
},
);

Difference: assertionFailure() records a failed assertion but allows other validations to continue. fail() stops execution immediately.