mirror of
https://github.com/nodejs/node.git
synced 2025-08-17 06:38:47 +02:00

This commit adds a t.assert.snapshot() method that implements snapshot testing. Serialization uses JSON.stringify() by default, but users can configure the serialization to meet their needs. PR-URL: https://github.com/nodejs/node/pull/53169 Fixes: https://github.com/nodejs/node/issues/48260 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
332 lines
11 KiB
JavaScript
332 lines
11 KiB
JavaScript
// Flags: --expose-internals --experimental-test-snapshots
|
|
/* eslint-disable no-template-curly-in-string */
|
|
'use strict';
|
|
const common = require('../common');
|
|
const fixtures = require('../common/fixtures');
|
|
const tmpdir = require('../common/tmpdir');
|
|
const {
|
|
snapshot,
|
|
suite,
|
|
test,
|
|
} = require('node:test');
|
|
const {
|
|
SnapshotManager,
|
|
defaultResolveSnapshotPath,
|
|
defaultSerializers,
|
|
} = require('internal/test_runner/snapshot');
|
|
const fs = require('node:fs');
|
|
|
|
tmpdir.refresh();
|
|
|
|
suite('SnapshotManager', () => {
|
|
test('uses default snapshot naming scheme', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
manager.resolveSnapshotFile();
|
|
t.assert.strictEqual(manager.snapshotFile, `${__filename}.snapshot`);
|
|
});
|
|
|
|
test('generates snapshot IDs based on provided name', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
|
|
t.assert.strictEqual(manager.nextId('foo'), 'foo 1');
|
|
t.assert.strictEqual(manager.nextId('foo'), 'foo 2');
|
|
t.assert.strictEqual(manager.nextId('bar'), 'bar 1');
|
|
t.assert.strictEqual(manager.nextId('baz'), 'baz 1');
|
|
t.assert.strictEqual(manager.nextId('foo'), 'foo 3');
|
|
t.assert.strictEqual(manager.nextId('foo`'), 'foo` 1');
|
|
t.assert.strictEqual(manager.nextId('foo\\'), 'foo\\ 1');
|
|
t.assert.strictEqual(manager.nextId('foo`${x}`'), 'foo`${x}` 1');
|
|
});
|
|
|
|
test('throws if snapshot file does not have exports', (t) => {
|
|
const fixture = fixtures.path(
|
|
'test-runner', 'snapshots', 'malformed-exports.js'
|
|
);
|
|
const manager = new SnapshotManager(fixture, false);
|
|
|
|
t.assert.throws(() => {
|
|
manager.resolveSnapshotFile();
|
|
manager.readSnapshotFile();
|
|
}, (err) => {
|
|
t.assert.strictEqual(err.code, 'ERR_INVALID_STATE');
|
|
t.assert.match(err.message, /Cannot read snapshot/);
|
|
t.assert.strictEqual(err.filename, manager.snapshotFile);
|
|
t.assert.match(err.cause.message, /Malformed snapshot file/);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test('provides a tip if snapshot file does not exist', (t) => {
|
|
const fixture = fixtures.path(
|
|
'test-runner', 'snapshots', 'this-file-should-not-exist.js'
|
|
);
|
|
const manager = new SnapshotManager(fixture, false);
|
|
|
|
t.assert.throws(() => {
|
|
manager.resolveSnapshotFile();
|
|
manager.readSnapshotFile();
|
|
}, /Missing snapshots can be generated by rerunning the command/);
|
|
});
|
|
|
|
test('throws if serialization cannot generate a string', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
const cause = new Error('boom');
|
|
const input = {
|
|
foo: 1,
|
|
toString() {
|
|
throw cause;
|
|
},
|
|
};
|
|
|
|
t.assert.throws(() => {
|
|
manager.serialize(input, [(value) => { return value; }]);
|
|
}, (err) => {
|
|
t.assert.strictEqual(err.code, 'ERR_INVALID_STATE');
|
|
t.assert.match(err.message, /The provided serializers did not generate a string/);
|
|
t.assert.strictEqual(err.input, input);
|
|
t.assert.strictEqual(err.cause, cause);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test('serializes values using provided functions', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
const output = manager.serialize({ foo: 1 }, [
|
|
(value) => { return JSON.stringify(value); },
|
|
(value) => { return value + '424242'; },
|
|
]);
|
|
|
|
t.assert.strictEqual(output, '\n{"foo":1}424242\n');
|
|
});
|
|
|
|
test('serialized values get cast to string', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
const output = manager.serialize(5, []);
|
|
|
|
t.assert.strictEqual(output, '\n5\n');
|
|
});
|
|
|
|
test('serialized values get escaped', (t) => {
|
|
const manager = new SnapshotManager(__filename, false);
|
|
const output = manager.serialize('fo\\o`${x}`', []);
|
|
|
|
t.assert.strictEqual(output, '\nfo\\\\o\\`\\${x}\\`\n');
|
|
});
|
|
|
|
test('reads individual snapshots from snapshot file', (t) => {
|
|
const fixture = fixtures.path('test-runner', 'snapshots', 'simple.js');
|
|
const manager = new SnapshotManager(fixture, false);
|
|
manager.resolveSnapshotFile();
|
|
manager.readSnapshotFile();
|
|
const snapshot = manager.getSnapshot('foo 1');
|
|
|
|
t.assert.strictEqual(snapshot, '\n{\n "bar": 1,\n "baz": 2\n}\n');
|
|
});
|
|
|
|
test('snapshot file is not read in update mode', (t) => {
|
|
const fixture = fixtures.path('test-runner', 'snapshots', 'simple.js');
|
|
const manager = new SnapshotManager(fixture, true);
|
|
manager.readSnapshotFile();
|
|
|
|
t.assert.throws(() => {
|
|
manager.getSnapshot('foo 1');
|
|
}, /Snapshot 'foo 1' not found/);
|
|
});
|
|
|
|
test('throws if requested snapshot does not exist in file', (t) => {
|
|
const fixture = fixtures.path('test-runner', 'snapshots', 'simple.js');
|
|
const manager = new SnapshotManager(fixture, false);
|
|
|
|
t.assert.throws(() => {
|
|
manager.getSnapshot('does not exist 1');
|
|
}, (err) => {
|
|
t.assert.strictEqual(err.code, 'ERR_INVALID_STATE');
|
|
t.assert.match(err.message, /Snapshot 'does not exist 1' not found/);
|
|
t.assert.strictEqual(err.snapshot, 'does not exist 1');
|
|
t.assert.strictEqual(err.filename, manager.snapshotFile);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test('snapshot IDs are escaped when stored', (t) => {
|
|
const fixture = fixtures.path('test-runner', 'snapshots', 'simple.js');
|
|
const manager = new SnapshotManager(fixture, false);
|
|
|
|
manager.setSnapshot('foo`${x}` 1', 'test');
|
|
t.assert.strictEqual(manager.getSnapshot('foo\\`\\${x}\\` 1'), 'test');
|
|
});
|
|
|
|
test('throws if snapshot file cannot be resolved', (t) => {
|
|
const manager = new SnapshotManager(null, false);
|
|
const assertion = manager.createAssert();
|
|
|
|
t.assert.throws(() => {
|
|
assertion('foo');
|
|
}, (err) => {
|
|
t.assert.strictEqual(err.code, 'ERR_INVALID_STATE');
|
|
t.assert.match(err.message, /Invalid snapshot filename/);
|
|
t.assert.strictEqual(err.filename, null);
|
|
return true;
|
|
});
|
|
});
|
|
|
|
test('writes the specified snapshot file', (t) => {
|
|
const testFile = tmpdir.resolve('test1.js');
|
|
const manager = new SnapshotManager(testFile, true);
|
|
manager.resolveSnapshotFile();
|
|
manager.setSnapshot('foo 1', 'foo value');
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
manager.writeSnapshotFile();
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), true);
|
|
});
|
|
|
|
test('creates snapshot directory if it does not exist', (t) => {
|
|
const testFile = tmpdir.resolve('foo/bar/baz/test2.js');
|
|
const manager = new SnapshotManager(testFile, true);
|
|
manager.resolveSnapshotFile();
|
|
manager.setSnapshot('foo 1', 'foo value');
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
manager.writeSnapshotFile();
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), true);
|
|
});
|
|
|
|
test('does not write snapshot file in read mode', (t) => {
|
|
const testFile = tmpdir.resolve('test3.js');
|
|
const manager = new SnapshotManager(testFile, false);
|
|
manager.resolveSnapshotFile();
|
|
manager.setSnapshot('foo 1', 'foo value');
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
manager.writeSnapshotFile();
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
});
|
|
|
|
test('throws if snapshot file cannot be written', (t) => {
|
|
const testFile = tmpdir.resolve('test4.js');
|
|
const error = new Error('boom');
|
|
const manager = new SnapshotManager(testFile, true);
|
|
manager.resolveSnapshotFile();
|
|
manager.snapshots['foo 1'] = { toString() { throw error; } };
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
t.assert.throws(() => {
|
|
manager.writeSnapshotFile();
|
|
}, (err) => {
|
|
t.assert.strictEqual(err.code, 'ERR_INVALID_STATE');
|
|
t.assert.match(err.message, /Cannot write snapshot file/);
|
|
t.assert.strictEqual(err.filename, manager.snapshotFile);
|
|
t.assert.strictEqual(err.cause, error);
|
|
return true;
|
|
});
|
|
|
|
t.assert.strictEqual(fs.existsSync(manager.snapshotFile), false);
|
|
});
|
|
});
|
|
|
|
suite('t.assert.snapshot() validation', () => {
|
|
test('options must be an object', (t) => {
|
|
t.assert.throws(() => {
|
|
t.assert.snapshot('', null);
|
|
}, /The "options" argument must be of type object/);
|
|
});
|
|
|
|
test('options.serializers must be an array if present', (t) => {
|
|
t.assert.throws(() => {
|
|
t.assert.snapshot('', { serializers: 5 });
|
|
}, /The "options\.serializers" property must be an instance of Array/);
|
|
});
|
|
|
|
test('options.serializers must only contain functions', (t) => {
|
|
t.assert.throws(() => {
|
|
t.assert.snapshot('', { serializers: [() => {}, ''] });
|
|
}, /The "options\.serializers\[1\]" property must be of type function/);
|
|
});
|
|
});
|
|
|
|
suite('setResolveSnapshotPath()', () => {
|
|
test('throws if input is not a function', (t) => {
|
|
t.assert.throws(() => {
|
|
snapshot.setResolveSnapshotPath('');
|
|
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
|
});
|
|
|
|
test('changes default snapshot output path', (t) => {
|
|
t.after(() => {
|
|
snapshot.setResolveSnapshotPath(defaultResolveSnapshotPath);
|
|
});
|
|
|
|
snapshot.setResolveSnapshotPath(() => { return 'foobarbaz'; });
|
|
const manager = new SnapshotManager(__filename, false);
|
|
manager.resolveSnapshotFile();
|
|
t.assert.strictEqual(manager.snapshotFile, 'foobarbaz');
|
|
});
|
|
});
|
|
|
|
suite('setDefaultSnapshotSerializers()', () => {
|
|
test('throws if input is not a function array', (t) => {
|
|
t.assert.throws(() => {
|
|
snapshot.setDefaultSnapshotSerializers('');
|
|
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
|
t.assert.throws(() => {
|
|
snapshot.setDefaultSnapshotSerializers([5]);
|
|
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
|
});
|
|
|
|
test('changes default serializers', (t) => {
|
|
t.after(() => {
|
|
snapshot.setDefaultSnapshotSerializers(defaultSerializers);
|
|
});
|
|
|
|
snapshot.setDefaultSnapshotSerializers([() => { return 'foobarbaz'; }]);
|
|
const manager = new SnapshotManager(__filename, false);
|
|
const output = manager.serialize({ foo: 1 });
|
|
t.assert.strictEqual(output, '\nfoobarbaz\n');
|
|
});
|
|
});
|
|
|
|
test('t.assert.snapshot()', async (t) => {
|
|
const fixture = fixtures.path(
|
|
'test-runner', 'snapshots', 'unit.js'
|
|
);
|
|
|
|
await t.test('fails prior to snapshot generation', async (t) => {
|
|
const child = await common.spawnPromisified(
|
|
process.execPath,
|
|
['--experimental-test-snapshots', fixture],
|
|
{ cwd: tmpdir.path },
|
|
);
|
|
|
|
t.assert.strictEqual(child.code, 1);
|
|
t.assert.strictEqual(child.signal, null);
|
|
t.assert.match(child.stdout, /# tests 3/);
|
|
t.assert.match(child.stdout, /# pass 0/);
|
|
t.assert.match(child.stdout, /# fail 3/);
|
|
t.assert.match(child.stdout, /Missing snapshots/);
|
|
});
|
|
|
|
await t.test('passes when regenerating snapshots', async (t) => {
|
|
const child = await common.spawnPromisified(
|
|
process.execPath,
|
|
['--test-update-snapshots', '--experimental-test-snapshots', fixture],
|
|
{ cwd: tmpdir.path },
|
|
);
|
|
|
|
t.assert.strictEqual(child.code, 0);
|
|
t.assert.strictEqual(child.signal, null);
|
|
t.assert.match(child.stdout, /tests 3/);
|
|
t.assert.match(child.stdout, /pass 3/);
|
|
t.assert.match(child.stdout, /fail 0/);
|
|
});
|
|
|
|
await t.test('passes when snapshots exist', async (t) => {
|
|
const child = await common.spawnPromisified(
|
|
process.execPath,
|
|
['--experimental-test-snapshots', fixture],
|
|
{ cwd: tmpdir.path },
|
|
);
|
|
|
|
t.assert.strictEqual(child.code, 0);
|
|
t.assert.strictEqual(child.signal, null);
|
|
t.assert.match(child.stdout, /tests 3/);
|
|
t.assert.match(child.stdout, /pass 3/);
|
|
t.assert.match(child.stdout, /fail 0/);
|
|
});
|
|
});
|