feat(common): add force-console option to nestfactory.create()

This commit is contained in:
mag123c
2025-08-07 10:27:06 +09:00
parent 0577b5476b
commit ff81fb1f57
3 changed files with 177 additions and 1 deletions

View File

@@ -0,0 +1,163 @@
import { ConsoleLogger, INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import * as sinon from 'sinon';
import { expect } from 'chai';
describe('ForceConsole Option', () => {
let app: INestApplication;
describe('When forceConsole is true', () => {
let consoleLogSpy: sinon.SinonSpy;
let consoleErrorSpy: sinon.SinonSpy;
let processStdoutSpy: sinon.SinonSpy;
let processStderrSpy: sinon.SinonSpy;
beforeEach(async () => {
// Spy on console and process methods
consoleLogSpy = sinon.spy(console, 'log');
consoleErrorSpy = sinon.spy(console, 'error');
processStdoutSpy = sinon.spy(process.stdout, 'write');
processStderrSpy = sinon.spy(process.stderr, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication({
forceConsole: true,
logger: ['log', 'error'],
});
await app.init();
});
afterEach(async () => {
consoleLogSpy.restore();
consoleErrorSpy.restore();
processStdoutSpy.restore();
processStderrSpy.restore();
await app.close();
});
it('should use console.log instead of process.stdout.write', async () => {
const logger = new ConsoleLogger('TestContext', { forceConsole: true });
logger.log('Test log message');
// Should use console.log when forceConsole is true
expect(consoleLogSpy.called).to.be.true;
// Verify console.log was called with the message
const consoleLogCalls = consoleLogSpy
.getCalls()
.filter(call =>
call.args.some(arg => String(arg).includes('Test log message')),
);
expect(consoleLogCalls.length).to.be.greaterThan(0);
});
it('should use console.error instead of process.stderr.write', async () => {
const logger = new ConsoleLogger('TestContext', { forceConsole: true });
logger.error('Test error message');
// Should use console.error when forceConsole is true
expect(consoleErrorSpy.called).to.be.true;
// Verify console.error was called with the message
const consoleErrorCalls = consoleErrorSpy
.getCalls()
.filter(call =>
call.args.some(arg => String(arg).includes('Test error message')),
);
expect(consoleErrorCalls.length).to.be.greaterThan(0);
});
it('should handle GET request with forceConsole option enabled', () => {
return request(app.getHttpServer()).get('/hello').expect(200);
});
});
describe('When forceConsole is false (default)', () => {
let consoleLogSpy: sinon.SinonSpy;
let consoleErrorSpy: sinon.SinonSpy;
let processStdoutSpy: sinon.SinonSpy;
let processStderrSpy: sinon.SinonSpy;
beforeEach(async () => {
// Spy on console and process methods
consoleLogSpy = sinon.spy(console, 'log');
consoleErrorSpy = sinon.spy(console, 'error');
processStdoutSpy = sinon.spy(process.stdout, 'write');
processStderrSpy = sinon.spy(process.stderr, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication({
logger: ['log', 'error'],
// forceConsole is not set, defaults to false
});
await app.init();
});
afterEach(async () => {
consoleLogSpy.restore();
consoleErrorSpy.restore();
processStdoutSpy.restore();
processStderrSpy.restore();
await app.close();
});
it('should not directly call console.log when forceConsole is false', async () => {
const logger = new ConsoleLogger('TestContext');
// Reset spy to ensure clean state
consoleLogSpy.resetHistory();
logger.log('Test log message');
// When forceConsole is false, should not call console.log
expect(consoleLogSpy.called).to.be.false;
});
it('should not directly call console.error when forceConsole is false', async () => {
const logger = new ConsoleLogger('TestContext');
// Reset spy to ensure clean state
consoleErrorSpy.resetHistory();
logger.error('Test error message');
// When forceConsole is false, should not call console.error
expect(consoleErrorSpy.called).to.be.false;
});
});
describe('When forceConsole is set via NestFactory.create', () => {
it('should apply forceConsole to the default logger', async () => {
const consoleLogSpy = sinon.spy(console, 'log');
const processStdoutSpy = sinon.spy(process.stdout, 'write');
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
const testApp = moduleRef.createNestApplication({
forceConsole: true,
});
await testApp.init();
// The logger created by NestFactory should respect forceConsole option
const logger = new ConsoleLogger('AppContext', { forceConsole: true });
logger.log('Application started');
expect(consoleLogSpy.called).to.be.true;
consoleLogSpy.restore();
processStdoutSpy.restore();
await testApp.close();
});
});
});

View File

@@ -67,4 +67,11 @@ export class NestApplicationContextOptions {
*/
instanceDecorator: (instance: unknown) => unknown;
};
/**
* If enabled, will force the use of console.log/console.error instead of process.stdout/stderr.write
* in the default ConsoleLogger. This is useful for test environments like Jest that can buffer console calls.
* @default false
*/
forceConsole?: boolean;
}

View File

@@ -10,6 +10,7 @@ import {
import { NestMicroserviceOptions } from '@nestjs/common/interfaces/microservices/nest-microservice-options.interface';
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { ConsoleLogger } from '@nestjs/common/services/console-logger.service';
import { Logger } from '@nestjs/common/services/logger.service';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { isFunction, isNil } from '@nestjs/common/utils/shared.utils';
@@ -299,9 +300,14 @@ export class NestFactoryStatic {
if (!options) {
return;
}
const { logger, bufferLogs, autoFlushLogs } = options;
const { logger, bufferLogs, autoFlushLogs, forceConsole } = options;
if ((logger as boolean) !== true && !isNil(logger)) {
Logger.overrideLogger(logger);
} else if (forceConsole) {
// If no custom logger is provided but forceConsole is true,
// create a ConsoleLogger with forceConsole option
const consoleLogger = new ConsoleLogger({ forceConsole: true });
Logger.overrideLogger(consoleLogger);
}
if (bufferLogs) {
Logger.attachBuffer();