Skip to content

Commit

Permalink
chore: re-enable console output in tests (#32883)
Browse files Browse the repository at this point in the history
Remove `silent: true` from the test config. `console.log()` is a time-honored debugging tradition, and the `silent: true` config makes that not work in a non-obvious way.

A nice behavior that many other testing frameworks have is to only print the console output of tests that fail. Use the "Jest Environments" feature to build an environment that replaces `console.log()` etc and buffers their output to only print it for failing tests.

This achieves the same goals of not polluting the terminal too much while running tests, while still allowing the ability to see the output of `console.log`.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
rix0rrr authored Jan 13, 2025
1 parent e7e908b commit a5bd76e
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
2 changes: 2 additions & 0 deletions packages/aws-cdk-lib/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ module.exports = {
statements: 55,
},
},

testEnvironment: './testhelpers/jest-bufferedconsole.ts',
};
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
"fast-check": "^3.22.0",
"jest": "^29.7.0",
"jest-each": "^29.7.0",
"jest-environment-node": "^29.7.0",
"lambda-tester": "^4.0.1",
"lodash": "^4.17.21",
"nock": "^13.5.5",
Expand Down
75 changes: 75 additions & 0 deletions packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* eslint-disable import/no-extraneous-dependencies */
/**
* A Jest environment that buffers outputs to `console.log()` and only shows it for failing tests.
*/
import type { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from '@jest/environment';
import { Circus } from '@jest/types';
import { TestEnvironment as NodeEnvironment } from 'jest-environment-node';

interface ConsoleMessage {
type: 'log' | 'error' | 'warn' | 'info' | 'debug';
message: string;
}

export default class TestEnvironment extends NodeEnvironment implements JestEnvironment<unknown> {
private log = new Array<ConsoleMessage>();
private originalConsole!: typeof console;

constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
super(config, context);

// We need to set the event handler by assignment in the constructor,
// because if we declare it as an async member TypeScript's type derivation
// doesn't work properly.
(this as JestEnvironment<unknown>).handleTestEvent = (async (event, _state) => {
if (event.name === 'test_done' && event.test.errors.length > 0 && this.log.length > 0) {
this.originalConsole.log(`[Console output] ${fullTestName(event.test)}\n`);
for (const item of this.log) {
this.originalConsole[item.type](' ' + item.message);
}
this.originalConsole.log('\n');
}

if (event.name === 'test_done') {
this.log = [];
}
}) satisfies Circus.EventHandler;
}

async setup() {
await super.setup();

this.log = [];
this.originalConsole = console;

this.global.console = {
...console,
log: (message) => this.log.push({ type: 'log', message }),
error: (message) => this.log.push({ type: 'error', message }),
warn: (message) => this.log.push({ type: 'warn', message }),
info: (message) => this.log.push({ type: 'info', message }),
debug: (message) => this.log.push({ type: 'debug', message }),
};
}

async teardown() {
this.global.console = this.originalConsole;
await super.teardown();
}
}

// DescribeBlock is not exported from `@jest/types`, so we need to build the parts we are interested in
type TestDescription = PartialBy<Pick<Circus.TestEntry, 'name' | 'parent'>, 'parent'>;

// Utility type to make specific fields optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

function fullTestName(test: TestDescription) {
let ret = test.name;
while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') {
ret = test.parent.name + ' › ' + fullTestName;
test = test.parent;
}
return ret;
}

6 changes: 0 additions & 6 deletions tools/@aws-cdk/cdk-build-tools/config/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,4 @@ module.exports = {
],
coveragePathIgnorePatterns: ['\\.generated\\.[jt]s$', '<rootDir>/test/', '.warnings.jsii.js$', '/node_modules/'],
reporters: ['default', ['jest-junit', { suiteName: 'jest tests', outputDirectory: 'coverage' }]],
/**
* This will still show us helpful information when running tests but remove console statements.
* The exception is when we use our custom logger in the CLI or when other processes are spun up
* within tests. It has no impact there.
*/
silent: true,
};

0 comments on commit a5bd76e

Please sign in to comment.