В bcrypt.js параметр rounds (он же cost factor) определяет количество итераций алгоритма хеширования. Он влияет напрямую на вычислительную сложность генерации хеша и, соответственно, на время выполнения операции.
2^{cost}
Именно экспоненциальный характер роста делает увеличение rounds критически важным с точки зрения безопасности и производительности. Увеличение cost на 1 примерно удваивает время вычисления хеша.
В практических реализациях bcrypt.js используется следующая модель:
Тестирование кода, использующего bcrypt, сталкивается с фундаментальным противоречием:
Хеширование пароля — одна из самых дорогих операций в типичном backend-тестировании. При большом количестве тестов даже cost = 12 может привести к значительному замедлению CI-пайплайна.
Поэтому управление rounds становится частью архитектуры тестовой стратегии, а не просто конфигурацией библиотеки.
Типичная практика заключается в разделении значений cost по окружениям:
const SALT_ROUNDS = {
test: 4,
development: 8,
production: 12
};
В тестовой среде значение intentionally занижается. Это допустимо, потому что цель тестов — проверка корректности логики, а не криптостойкости.
Мокирование bcrypt кажется простым решением:
jest.mock('bcryptjs', () => ({
hash: jest.fn(() => Promise.resolve('hashed_password')),
compare: jest.fn(() => Promise.resolve(true))
}));
Однако такой подход создаёт ложное чувство корректности системы:
Моки уместны только для unit-тестов бизнес-логики, но не для криптографических операций.
Более надёжная стратегия — использование реального bcrypt.js с контролируемыми rounds.
Пример базового теста:
import bcrypt from 'bcryptjs';
test('password hashing and comparison works', async () => {
const password = 'securePassword123';
const saltRounds = 4;
const hash = await bcrypt.hash(password, saltRounds);
const result = await bcrypt.compare(password, hash);
expect(result).toBe(true);
});
Ключевой момент: проверяется не значение хеша, а его поведенческая корректность.
bcrypt использует соль, поэтому хеш одного и того же пароля всегда различается:
password + salt → hash1
password + different salt → hash2
Поэтому сравнение строк хеша в тестах бессмысленно. Единственный
стабильный критерий — результат compare().
Практическая схема конфигурации:
const getSaltRounds = () => {
const env = process.env.NODE_ENV;
if (env === 'test') return 4;
if (env === 'development') return 8;
return 12;
};
Использование в коде:
const saltRounds = getSaltRounds();
const hash = await bcrypt.hash(password, saltRounds);
Такой подход позволяет:
Разделение тестов становится критическим:
Пример интеграционного теста:
test('user registration stores valid bcrypt hash', async () => {
const password = 'myPassword';
const hash = await bcrypt.hash(password, 4);
const isValid = await bcrypt.compare(password, hash);
expect(isValid).toBe(true);
expect(hash).not.toBe(password);
});
При большом наборе тестов даже cost = 6 может замедлить pipeline. Поэтому часто применяют гибридный подход:
Так достигается баланс между скоростью и реалистичностью.
1. Сравнение хешей напрямую
expect(hash).toBe(expectedHash); // нестабильно
Ошибка: bcrypt всегда генерирует разные значения из-за соли.
2. Использование production rounds в тестах
Это приводит к резкому замедлению CI без пользы.
3. Полное мокирование bcrypt везде
Ломает доверие к тестам как к механизму валидации безопасности.
Иногда полезно измерять фактическое время хеширования:
const start = Date.now();
await bcrypt.hash('password', 4);
const duration = Date.now() - start;
Это позволяет отслеживать деградацию производительности при изменении зависимостей или окружения Node.js.
bcrypt — CPU-bound операция. При массовом запуске тестов возможна перегрузка event loop.
Решения:
maxWorkers)Основная практическая модель тестирования bcrypt.js строится вокруг контроля cost factor, отказа от прямого сравнения хешей и разделения уровней тестирования по глубине проверки и стоимости выполнения.