@times-components/jest-serializer

3.13.9 • Public • Published

jest-serializer

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.

Supported

  • Jest@21.2.1
  • Node@8.11.3

Getting started

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.

Serializers

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

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.

Enzyme Serializers

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

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

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

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

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

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

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

minimalWeb: Serializer

A convenience serializer to remove the plethora of props 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.

replace

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

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

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 composed 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.

Printers

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.

print

Simply takes the given Node and uses Jest's standard serialize method;

stylePrinter

A custom printer that works with hoistStyle and rnw which takes their accumulated "bag" of styles and prints them in a style block

Patterns and Practices

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.

Package Sidebar

Install

npm i @times-components/jest-serializer

Weekly Downloads

2,738

Version

3.13.9

License

BSD-3-Clause

Unpacked Size

92.7 kB

Total Files

15

Last publish

Collaborators

  • marcincuber
  • newsuk
  • news-tools