A suite of tools to enable you to "curate" the "perfect" Jest snapshot. See this blog post for more details on why this package was developed. The primary focus is on JSX in snapshots rather than other data structures.
It's designed to aid you with your tests that use Jest with no other requirements * other than importing the desired serializers for your needs.
- Jest@21.2.1
- Node@8.11.3
yarn add @times-components/jest-serializer --dev
within your tests you can now use code that looks like this:
import {
addSerializers,
enzymeRenderedSerializer
} from "@times-components/jest-serializer";
addSerializers(expect, enzymeRenderedSerializer());
and any snapshot tests that run in the context of this serializer will generate snapshots based on these custom serializers. The context being the "test file" currently under test.
addSerializers(expect: JestExpect, ...serializers: Serializer[]): void
Implicitly add your custom Jest serializer(s) by using addSerializers
. This
takes Jest's global expect
followed by any serializers you would like to use
e.g.
addSerializers(expect, serializer1, serializer2);
These are the serializers that are currently provided:
compose(printer: Printer, ...transformers: Transformer[]): Serializer
A special type of serializer that takes a printer
followed by the transformers
you would like to apply.
A transformer is any of the serializers below but with "Transform" appended. This allows us to only visit each node once and perform each transformation (from left to right) on it. Accordingly, order is important for transformers that have side-effects that another transformer was relying on e.g.
compose(
stylePrinter,
minimalWebTransform
);
is the same as running the minimalWeb
and rnw
serializers together, and if
rnw
had a dependency on what minimalWeb
was removing, you would need to swap
them round.
When using enzyme in conjunction with enzyme-to-json you can use mount to give you access to these modes. They've been wrapped in the serializers below for convenience and clarity
enzymeRenderedSerializer(): Serializer
Gives you your mounted Enzyme component tree as if it was rendered. i.e.
<div>
s and <span>
s. This useful if you don't care to manipulate the tree in
anyway.
enzymeRootSerializer(): Serializer
While you may have mounted the component you can still ask to just serialize the root component only. May be useful if you have a mixture of snapshot requirements involving both parent and children. However you may use Enzyme's own API to pick the correct thing to snapshot to begin with.
enzymeTreeSerializer(): Serializer
The majority of the time you may be using the rendered version but this one
allows you to see the "host" components and therefore allow you to refine the
snapshot in various ways based on them. Can be used in conjunction with the
replace
serializer to keep the snapshots focused.
e.g.
<ParentComponent>
<div>
<ChildComponent>
<span>Hello world!</span>
</ChildComponent>
</div>
</ParentComponent>
flattenStyle: Serializer
If you're using Enzyme you may find that styles (depending on how they've been written) may appear as a number (a pointer to a RN stylesheet style) or an Array (a composition of styles). This squashes it all for you, and allows you to see the styles as intended.
On web you may want to also use hoistStyle
to clean up the snapshot noise
further.
hoistStyle: Serializer
It makes sense to show a snapshot with inline styles on web,
it's perhaps a little more idiomatic to see it as a className
and the style to
reside in a style
block. This is a better reflection of how it'll be rendered
on the platform (RNW magic) and keeps the snapshots a little leaner too.
For the styles to be shown, use with compose
and the stylePrinter
.
minimalise((value: Object, key: string): boolean): Serializer
Takes a function that allows you to omit certain properties that you're not
interested in seeing. Often snapshots will spew out many props
that are
irrelevant to your tests (especially on RN), this serializer is a key tool in
keeping them focused e.g.
addSerializers(expect, minimalise((value, key) => key === "style"));
will strip all styles from your JSX nodes.
minimalWeb: Serializer
A convenience serializer to remove the plethora of prop
s React prints by
default that may change React versions but have
no baring on your tests. In particular it removes [Function]
values because
they should be tested with either interactive tests and/or a type system rather
than with snapshots.
Refer to the code for the latest removed values.
type Replacer = (node: ReactNode, props: ReactProps, children: ReactChildren): { node?: ReactNode, props?: ReactProps, children?: ReactChildren };
replace({ [ComponentName: string]: Replacer }): Serializer
A serializer that may be best suited to jest.mock
but in some cases you may
find it desirable to simply switch out bits of your tree for brevity and focus
independent of what jest.mock
provides or because you don't want to globally
mock the dependency. jest-mock
also doesn't play well with web or named member
imports.
replaceProp((value: Object, key: string): Object): Serializer
Similar to the minimalise
serializer, rather than omitting values you can
replace them. This is handy for occasions when a prop value has a very long
completely unreadable value that you would like to "monitor" and see on a PR
that a change has been made but at the same time not destroy the snapshots
readability.
rnw(AppRegistry, string[]): Serializer
If you're using Jest snapshots on the web platform to ensure your components are rendering
the expected web output, there'll be a lot of className
noise.
Use this serializer with any style properties you're interested in, and when
compose
d with the stylePrinter
you can see these printed in a style
block
e.g.
compose(stylePrinter);
will assign a generic className
to the Node and print the styles with that
className
, but only for color
and fontSize
due to RNW producing many stock
styles.
AppRegistry
needs to come from the consumer where the styles have been cached.
Currently there are two types of "printers". A printer has the type:
(serialize: ((obj: Object): string), accum: {[key: string]: string}, element: ReactNode): string
The custom serializers can build up something they'd like to "accumulate" over the whole snapshot such as styles, and the printer decides how to print them.
Simply takes the given Node
and uses Jest's standard serialize
method;
A custom printer that works with hoistStyle
and rnw
which takes their
accumulated "bag" of styles and prints them in a style
block
Using React Test Renderer tends to be your best bet. This
doesn't give you "host" objects for the replace
serializer leaving you with
jest.mock
. It's currently advisable to mock all your dependencies even if you
have the best intentions with "integration" testing. They usually lead to a
multitude of snapshot updates you don't care about which dulls your interest in
reviewing them at the PR level with little benefit.
For web using Enzyme with mount
and a mixture of the
enzymeRenderedSerializer
and enzymeTreeSerializer
should give you everything
you need to start with. As with the curation of any snapshot, it's best to start
with the raw output, then prune it iteratively to make sure you both don't miss
something and/or can make informed decision on where you want to split your
snapshots.
It's particularly recommended to split out styling from rendering in tests due
to the same "too many updates" problem. It's much more likely that the snapshots
will be valued if they only update when they're supposed to and changing
padding
shouldn't change the snapshot test for "renders capital letters". It
also highlights on a PR when a styling change is made and the style snapshot is
not updated to the reviewer. This would indicate it's not being correctly
captured which can get lost with large "big picture" renders.
Another pattern you want to look out for is repeated props throughout a snapshot which probably means they should be removed and one test should represent them. It's discretionary but probably means the snapshots aren't focused on the tests enough.
On web you're similarly going to be looking at minimalWeb
, hoistStyle
,
minimalise
, and rnw
.
BE AWARE! Under the hood of addSerializer
it's using
expect.addSnapshotSerializer
. There's no expect.removeSnapshotSerializer
and
they don't compose particularly well due to the various side-effects after
they've been applied. While it is possible to get a coincidental balance of
serializers at the test level (order very much dependent), try and run your
desired serializers per test file for sanity.