From 5d04bc7652940a1fc30cbd5e090ed8f8fabc9f9f Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Sat, 8 Feb 2025 22:58:27 -0800 Subject: [PATCH] Silence console functions on stdout and instead assert they occurred This approach makes the test runner output cleaner, and also tests the console calls occurred as expected. --- test/normalization.test.js | 33 ++++++++++++++++++---------- test/utils.js | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/test/normalization.test.js b/test/normalization.test.js index cd52b58..3463a23 100644 --- a/test/normalization.test.js +++ b/test/normalization.test.js @@ -1,7 +1,7 @@ import { suite, test } from 'node:test'; import { deepStrictEqual } from 'node:assert/strict'; -import { csvStreamEqual, csvStreamEqualWritable, createCSVMockStream } from './utils.js'; +import { csvStreamEqual, csvStreamEqualWritable, createCSVMockStream, assertConsole } from './utils.js'; import { CSVReader } from '../src/core.js'; import { toCamelCase, expandScientificNotation, CSVNormalizer, CSVDenormalizer } from '../src/normalization.js'; @@ -73,11 +73,14 @@ suite('CSVNormalizer.fixExcelNumber', { concurrency: true }, () => { { name: 'fixes accounting mangling integer', input: ' $123.00 ', output: 123 }, { name: 'fixes accounting mangling number', input: ' $123.45 ', output: 123.45 }, { name: 'fixes accounting mangling with commas', input: ' $10,000,000.00 ', output: 10000000 }, - { name: 'passes through non-number value', input: 'abc', output: 'abc' }, + { name: 'passes through non-number value', input: 'abc', output: 'abc', consoleCounts: { warn: 1 } }, { name: 'passes through blank value', input: '', output: '' }, ]; - for (const { name, input, output } of vectors) { - test(name, { concurrency: true }, () => { + for (const { name, input, output, consoleCounts } of vectors) { + test(name, { concurrency: true }, (t) => { + if (consoleCounts !== undefined) { + assertConsole(t, consoleCounts); + } deepStrictEqual(CSVNormalizer.fixExcelNumber(input), output); }); } @@ -92,11 +95,14 @@ suite('CSVNormalizer.fixExcelBigInt', { concurrency: true }, () => { { name: 'fixes accounting mangling integer', input: ' $123.00 ', output: '123' }, { name: 'fixes accounting mangling number (truncated)', input: ' $123.45 ', output: '123' }, { name: 'fixes accounting mangling BigInt with commas', input: ' $1,123,123,123,123,123,123.12 ', output: '1123123123123123123' }, - { name: 'passes through non-BigInt values', input: 'abc', output: 'abc' }, + { name: 'passes through non-BigInt values', input: 'abc', output: 'abc', consoleCounts: { warn: 1 } }, { name: 'passes through blank value', input: '', output: '' }, ]; - for (const { name, input, output } of vectors) { - test(name, { concurrency: true }, () => { + for (const { name, input, output, consoleCounts } of vectors) { + test(name, { concurrency: true }, (t) => { + if (consoleCounts !== undefined) { + assertConsole(t, consoleCounts); + } deepStrictEqual(CSVNormalizer.fixExcelBigInt(input), output); }); } @@ -116,11 +122,15 @@ suite('CSVNormalizer.fixExcelDate', { concurrency: true }, () => { { name: 'fixes number mangling (43915.31372)', input: '43915.31372', output: '2020-03-25T07:31:45.407Z' }, { name: 'fixes number mangling (44067.63)', input: '44067.63', output: '2020-08-24T15:07:11.999Z' }, { name: 'fixes number mangling (44309.63502)', input: '44309.63502', output: '2021-04-23T15:14:25.728Z' }, - { name: 'passes through non-date values', input: 'abc', output: 'abc', raw: true }, + { name: 'passes through non-date values', input: 'abc', output: 'abc', raw: true, consoleCounts: { warn: 1 } }, { name: 'passes through blank value', input: '', output: '', raw: true }, ]; - for (const { name, input, output, raw = false } of vectors) { - test(name, { concurrency: true }, () => { + for (const { name, input, output, raw = false, consoleCounts } of vectors) { + test(name, { concurrency: true }, (t) => { + if (consoleCounts !== undefined) { + assertConsole(t, consoleCounts); + } + if (raw) { deepStrictEqual(CSVNormalizer.fixExcelDate(input), output); } @@ -197,7 +207,8 @@ suite('CSVNormalizer', { concurrency: true }, () => { ['a', 'b'] ])); }); - test('can ignore incorrect data types', { concurrency: true }, async () => { + test('can ignore incorrect data types', { concurrency: true }, async (t) => { + assertConsole(t, { warn: 2 }); await createCSVMockStream([ ['columnA', 'columnB'], ['a', 'b'] diff --git a/test/utils.js b/test/utils.js index a6998fb..ea1a7c4 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,6 +2,7 @@ import { deepStrictEqual, notStrictEqual } from 'node:assert/strict'; import { tmpdir } from 'node:os'; import { mkdtemp, open } from 'node:fs/promises'; import { normalize } from 'node:path'; +/** @import { TestContext } from 'node:test'; */ /** * Log an entire stream along with the field count for each row and the total row count. @@ -103,6 +104,50 @@ export function csvStreamNotEqualWritable(csv) { }); } +/** + * @typedef AssertConsoleCounts Object containing the total number of times `console.log`, `console.info`, `console.warn`, + * and `console.error` are expected to be called. + * @property {number} [log=0] The number of times `console.log` is expected to be called. + * @property {number} [info=0] The number of times `console.info` is expected to be called. + * @property {number} [warn=0] The number of times `console.warn` is expected to be called. + * @property {number} [error=0] The number of times `console.error` is expected to be called. + */ + +/** + * Silences console functions during a test and asserts a given number of calls were made during the test. + * + * **Important:** Console will only be silenced AFTER this function is called, and only until the test ends. Call this function + * early in the test to avoid unexpected console calls. + * + * @param {TestContext} context A Node.js `TestContext` for a test case. + * @param {AssertConsoleCounts} [expected] Object containing counts for the total number of times console functions are expected + * to be called. + */ +export function assertConsole(context, expected = {}) { + expected.log ??= 0; + expected.info ??= 0; + expected.warn ??= 0; + expected.error ??= 0; + const actual = { + log: 0, + info: 0, + warn: 0, + error: 0 + } + + context.mock.method(console, 'log', () => actual.log++); + context.mock.method(console, 'info', () => actual.info++); + context.mock.method(console, 'warn', () => actual.warn++); + context.mock.method(console, 'error', () => actual.error++); + + context.after(() => { + deepStrictEqual(actual.log, expected.log, `expected ${expected.log} console.log calls, got ${actual.log}`); + deepStrictEqual(actual.info, expected.info, `expected ${expected.info} console.info calls, got ${actual.info}`); + deepStrictEqual(actual.warn, expected.warn, `expected ${expected.warn} console.warn calls, got ${actual.warn}`); + deepStrictEqual(actual.error, expected.error, `expected ${expected.error} console.error calls, got ${actual.error}`); + }); +} + /** * Simulate a stream of CSV data for test mocking purposes. * -- 2.47.3