Перейти к основному содержимому

Тестирование логирования

Подключение

Для тестов следует подключать специальный модуль — LocalLoggerModule.forTesting(). Он сохраняет логи в памяти и предоставляет к ним доступ через TestingLogger.

Это позволяет:

  • перехватывать все вызовы логгера внутри тестируемого кода;
  • сохранять их в память вместо вывода в консоль;
  • проверять, какие именно логи были собраны за время теста.

Пример подключения через NestJS TestingModule:

let logger: TestingLogger;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [LocalLoggerModule.forTesting()],
}).compile();

logger = module.get(TestingLogger);
});

После такого подключения любые вызовы вида:

this.logger.log('Project created');
this.logger.error('Project update failed');

будут сохранены внутри TestingLogger, и вы сможете проверить их в тестах при помощи матчеров:

logger.withMessage('Project created').withLevel('info').toBeLoggedOnce();
logger.withMessage('Project update failed').withLevel('error').toBeLogged();

Цепочка фильтров

Методы фильтрации (withMessage, withLevel, withContext, withDetails) можно вызывать цепочкой, для более точного сопоставления логов:

logger
.withMessage('Project created')
.withLevel('info')
.withContext('Projects')
.withDetails({ user: 5 })
.toBeLoggedOnce();

Метод withDetails поддерживает частичное сравнение объекта, а также может проверять поля level, context и message:

logger
.withMessage('Project created')
.withDetails({
level: 'info',
context: 'Projects',
user: 5,
})
.toBeLoggedOnce();

Просмотр найденных записей

Если нужно отладить фильтр, и просмотреть, какие записи были найдены по текущему фильтру, можно использовать метод showLogs():

logger.withLevel('error').showLogs().toBeLogged();

Методы проверки

МетодОписание
toBeLogged()Проверяет, что хотя бы одна запись соответствует фильтру.
toBeNotLogged()Проверяет, что таких записей нет.
toBeLoggedOnce()Проверяет, что запись встречается ровно один раз.
toBeLoggedTimes(n)Проверяет, что запись встречается n раз.

Все методы выбрасывают ошибку, если условие не выполняется.

Важно: Методы проверки обязательно должны вызываться, иначе проверки так и не будет.

Группирование проверок

Если в тесте нужно проверить несколько событий с общим набором полей, можно использовать такой приём:

const rbacLogger = logger.withContext('RBAC');

rbacLogger.withMessage('Access granted').toBeLoggedTimes(2);

rbacLogger.withMessage('Access denied').toBeLoggedOnce();

Это удобно для группировки проверок по контексту или другим общим параметрам.

Примеры использования

Проверка бизнес-события

it('должен логировать создание проекта', () => {
service.createProject({ user: 7 });

logger
.withMessage('Project created')
.withLevel('info')
.withDetails({ user: 7 })
.toBeLoggedOnce();
});

Проверка ошибок

it('должен логировать ошибку при невалидном статусе', () => {
service.updateProjectStatus(42, 'invalid');

logger.withMessage('Project update failed').withLevel('warn').toBeLogged();
});

Проверка отсутствия логов

it('не должен логировать ничего при успешном выполнении без событий', () => {
service.doNothing();

logger.any().toBeNotLogged();
});

Очистка

beforeEach(() => {
logger.clear();
});

Используйте clear() для сброса состояния между тестами, если экземпляр TestingLogger переиспользуется в нескольких сценариях.

Рекомендации

  • TestingLogger предназначен только для тестов — в рабочем коде используется обычный Logger.
  • Проверяйте факт логирования, а не реализацию — тесты должны оставаться устойчивыми к внутренним изменениям.
  • Используйте toBeNotLogged() для сценариев, где событие не должно быть зафиксировано.