При тестировании модулей, работающих с зашифрованными токенами Iron
(@hapi/iron), ключевая проблема заключается в том, что
операции seal и unseal зависят от
криптографического ключа, настроек безопасности и иногда временных
параметров. Прямое использование реальной криптографии в юнит-тестах
приводит к нестабильности, медленному выполнению и сложной поддержке
тестового окружения.
Библиотека Iron используется для «упаковки» объекта в защищённую строку и последующего восстановления исходного объекта.
Типичный сценарий:
import Iron from '@hapi/iron';
const password = 'super-secure-password';
const data = {
userId: 42,
role: 'admin'
};
const sealed = await Iron.seal(data, password, Iron.defaults);
const unsealed = await Iron.unseal(sealed, password, Iron.defaults);
В реальном приложении это полезно для хранения сессионных данных без обращения к базе. Однако в тестах это создаёт избыточную нагрузку и ненужную зависимость от реализации криптографии.
Мокирование необходимо в случаях, когда:
seal и unsealСамый простой подход — подмена функций библиотеки:
jest.mock('@hapi/iron', () => ({
seal: jest.fn(async (obj) => {
return `sealed-${JSON.stringify(obj)}`;
}),
unseal: jest.fn(async (sealed) => {
const json = sealed.replace('sealed-', '');
return JSON.parse(json);
}),
defaults: {}
}));
Такой подход делает тесты полностью предсказуемыми и исключает криптографическую зависимость.
Более архитектурно правильный способ — не импортировать Iron напрямую в бизнес-логику.
export function createTokenService({ iron, password }) {
return {
async sign(payload) {
return iron.seal(payload, password, iron.defaults);
},
async verify(token) {
return iron.unseal(token, password, iron.defaults);
}
};
}
Тест:
const mockIron = {
seal: jest.fn(async (obj) => `mocked-${obj.userId}`),
unseal: jest.fn(async (token) => ({ userId: Number(token.split('-')[1]) })),
defaults: {}
};
const service = createTokenService({
iron: mockIron,
password: 'test'
});
Такой подход полностью отделяет тест от библиотеки.
Иногда достаточно не мокать библиотеку, а заменить сами токены на фиктивные значения:
const fakeToken = 'iron:mock.token.value';
Используется в интеграционных тестах, где важна структура запросов, а не криптография.
Критическая ошибка — использование реального password
или key из production в тестах.
Правильный подход:
const TEST_PASSWORD = 'test-password-only';
или через переменные окружения:
process.env.IRON_PASSWORD = 'test-password';
Если мок возвращает не тот формат, что реальный unseal,
могут возникнуть ошибки в логике:
Рекомендуется сохранять структуру максимально близкой к реальной.
Iron фактически проверяет возможность сериализации объекта. При полном мокировании эта проверка исчезает, и можно случайно пропустить ошибки в данных.
Решение — добавлять отдельные тесты без моков для критических сценариев.
Оригинальные методы Iron асинхронны. Мок должен сохранять это поведение:
seal: jest.fn(async () => 'token')
Иначе тесты могут давать ложные результаты.
Иногда требуется сохранить оригинальную реализацию, изменив только поведение:
jest.mock('@hapi/iron', () => {
const actual = jest.requireActual('@hapi/iron');
return {
...actual,
seal: jest.fn(actual.seal),
unseal: jest.fn(actual.unseal)
};
});
Такой подход полезен, когда важно тестировать взаимодействие, но контролировать результат.
Для проверки обработки ошибок необходимо имитировать сбои:
iron.unseal.mockRejectedValue(new Error('Invalid token'));
или
seal: jest.fn(async () => {
throw new Error('Encryption failed');
});
Это позволяет тестировать устойчивость системы к повреждённым токенам.
При правильной архитектуре тесты не должны зависеть от Iron вообще.
Пример:
function isAdmin(session) {
return session.role === 'admin';
}
Тест:
expect(isAdmin({ role: 'admin' })).toBe(true);
Здесь мокирование токенов уже не требуется — декодирование вынесено за пределы логики.
Мокирование Iron обычно применяется только в:
В e2e-тестах мокирование часто отключается, чтобы проверить реальный поток данных:
Часто удобно выносить мок в отдельный файл:
// __mocks__/@hapi/iron.js
module.exports = {
seal: jest.fn(async (obj) => `mock-${obj.userId}`),
unseal: jest.fn(async (token) => ({ userId: Number(token.replace('mock-', '')) })),
defaults: {}
};
Это упрощает поддержку тестов и убирает дублирование.
Полное мокирование ускоряет тесты, но снижает достоверность. Полное отсутствие моков повышает точность, но делает тесты медленными и нестабильными.
Практическое решение:
Так сохраняется баланс между скоростью и надёжностью проверки поведения системы.