В системе тестирования и валидации Vest хуки выполняются в рамках жизненного цикла сьютов и тестов. Исключения в них можно разделить на несколько категорий:
1. Синхронные ошибки выполнения
throw2. Асинхронные ошибки
async хукахPromise rejectionawait)3. Ошибки сторонних зависимостей
4. Ошибки валидации
enforce или testХуки в Vest выполняются в строго определённой последовательности, и поведение при ошибках зависит от типа хука и контекста.
Эти хуки выполняются вокруг каждого теста:
beforeEach прерывает выполнение текущего
тестаafterEach не отменяет результат теста, но
фиксируется в результате сьютаПример:
import { suite } from 'vest';
const s = suite('example', () => {
beforeEach(() => {
if (!globalThis.config) {
throw new Error('Config not initialized');
}
});
test('field', 'validation message', () => {
// тест не выполнится, если beforeEach выбросил исключение
});
afterEach(() => {
cleanupTempState();
});
});
Используется для подготовки окружения. Исключения здесь критичны:
Рекомендуется изолировать потенциально опасные операции:
beforeAll(async () => {
try {
await connectToDatabase();
} catch (err) {
throw new Error('Database connection failed: ' + err.message);
}
});
Используется для освобождения ресурсов:
Практика:
afterAll(async () => {
try {
await closeDatabase();
} catch (err) {
console.error('Cleanup failed:', err);
}
});
Внутри test ошибки могут возникать как через
enforce, так и через обычные исключения.
test('username', 'Invalid username', () => {
if (typeof value !== 'string') {
throw new Error('Username must be string');
}
});
Такие ошибки автоматически регистрируются как провал теста.
enforce генерирует структурированные ошибки:
test('email', 'Invalid email', () => {
enforce(value).isNotEmpty();
enforce(value).matches(/@/);
});
Если условие не выполнено, Vest не выбрасывает необработанное исключение, а записывает ошибку в результат сьюта.
Асинхронные операции требуют особого внимания.
beforeEach(async () => {
await fetchConfig(); // может reject
});
Если fetchConfig завершится с ошибкой, сьют
прервётся.
beforeEach(async () => {
try {
await fetchConfig();
} catch (err) {
throw new Error('Failed to load config');
}
});
afterEach(async () => {
try {
await rollbackChanges();
} catch (err) {
console.warn('Rollback failed:', err);
}
});
Иногда необходимо не прерывать выполнение сьюта при ошибке вспомогательной логики.
beforeEach(() => {
try {
initOptionalService();
} catch (err) {
// ошибка игнорируется
}
});
Такой подход используется только для некритичных зависимостей.
Vest различает:
Для сохранения консистентности рекомендуется преобразовывать неожиданные ошибки:
test('profile', 'Profile validation failed', async () => {
try {
await validateProfile(value);
} catch (err) {
throw new Error('Profile service error: ' + err.message);
}
});
Часто хуки используют вспомогательные функции, что увеличивает риск неконтролируемых исключений.
beforeEach(() => {
setup(); // внутри может быть throw
});
function safeSetup() {
try {
setup();
} catch (err) {
throw new Error('Setup failed');
}
}
beforeEach(() => {
safeSetup();
});
При возникновении ошибки:
При последовательных async-хуках важно учитывать:
Пример проблемного сценария:
beforeEach(async () => {
await stepOne();
await stepTwo(); // падение здесь оставляет частичное состояние
});
Решение — атомарные операции:
beforeEach(async () => {
try {
await runAllSetupSteps();
} catch (err) {
throw new Error('Setup chain failed');
}
});
Любые I/O операции выполняются в try/catch блоках:
afterEach(async () => {
try {
await fsCleanup();
} catch (err) {
logError(err);
}
});
Хуки должны оставаться тонкими обёртками над функциями:
beforeEach(() => initContext());
afterEach(() => destroyContext());
function handleHookError(context, err) {
throw new Error(`[${context}] ${err.message}`);
}
beforeEach(() => {
try {
init();
} catch (err) {
handleHookError('beforeEach:init', err);
}
});
process.on('unhandledRejection', (err) => {
console.error('Unhandled rejection in Vest suite:', err);
});
Если несколько хуков выбрасывают исключения:
Основные принципы: