Improved snapshot testing in Jest with alpha-serializer
- Published
Jest test runner has great feature for snapshot testing. A concept that greatly improves the process of writing tests. I'd like to briefly explain how it works and what you can do to improve even more.
How snapshot testing work
Let's start with an example
function testSubject() {
return {
foo: 'bar',
number: 20,
avast: 'test'
}
}
describe('foo', () => {
it('example', () => {
expect(testSubject()).toMatchSnapshot();
});
});
Once you run the test for the first time, jest checks whether a snapshot for given test exist. If not then snapshot is created with current result, otherwise compares saved data with current result.
Example snapshot
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`foo example 1`] = `
Object {
"avast": "test",
"foo": "bar",
"number": 20,
}
`;
As you see jest simply serializes the data in a readable form and saves it on a disk. pretty-format it a tool used by jest for serialization and by default is able to handle a lot of types such as dates, functions, regexps etc.
Custom types
As your application grows you might need decent tool for symmetric serialization like alpha-serializer to handle custom types that might not be well supported by pretty-format such as custom errors.
class ValidationError extends Error {
constructor(message: string, readonly errors: { [key: string]: string[] }) {
super(message);
this.message = message;
this.name = 'ValidationError';
}
}
describe('foo', () => {
it('example', () => {
expect(testSubject()).toMatchSnapshot();
});
it('unsupported types', () => {
expect({
validationError: new ValidationError('invalid data', {
name: ['cannot be blank'] // this will be ignored in assertion as they're not properly serialized
})
}).toMatchSnapshot();
});
});
// snapshot result
exports[`foo unsupported types 1`] = `
Object {
"validationError": [ValidationError: invalid data],
}
`;
Note that property errors
does not exist in the snapshot and that will cause the test to pass incorrectly even though
the errors
property might be different.
In order to fix that we can use alpha-serializer to customize process of serialization for certain types.
import {normalizer, Serializable, serialize} from 'alpha-serializer';
expect.addSnapshotSerializer({
test(value: any) {
// check if given type has defined normalization
return normalizer.hasNormalization(value);
},
print(val: any, serializer) {
// normalize data before serialization
return serializer(normalizer.normalize(val))
// you might also use serialization from alpha-serializer
// return serialize(val)
}
});
@Serializable({
normalizer(error: ValidationError) {
return {message: error.message, errors: error.errors};
}
})
class ValidationError extends Error {
constructor(message, readonly errors) {
super(message);
this.message = message;
this.name = 'ValidationError';
}
}
Run your test again and compare the saved snapshot
exports[`foo unsupported types 1`] = `
Object {
"validationError": Object {
"@type": "ValidationError",
"value": Object {
"errors": Object {
"name": Array [
"cannot be blank",
],
},
"message": "invalid data",
},
},
}
`;
You can also use alpha-serializer-jest that makes whole setup for you.
Now your custom types are handled properly :)