lmiguelvargasf
lmiguelvargasf

Reputation: 69983

Using toJSON or not with Snapshot testing with Jest

I am new to React, and I am testing my components. When implementing snapshot testing I have found these two approaches:

Approach 1

import React from 'react'
import renderer from 'react-test-renderer'
import Projects from './Projects'

it('renders Projects component properly', () => {
  const tree = renderer.create(<Projects />)
  expect(tree).toMatchSnapshot()
})

Approach 2

import React from 'react'
import renderer from 'react-test-renderer'
import Projects from './Projects'

it('renders Projects component properly', () => {
  const component = renderer.create(<Projects />)
  const tree = component.toJSON()
  expect(tree).toMatchSnapshot()
})

Is it important to call toJSON in the component (approach 2) or can you just pass it directly to check toMatchSnapshot (approach 1)? Can somebody explain me the difference between both approaches in terms of performance?

Upvotes: 3

Views: 3545

Answers (1)

Shlang
Shlang

Reputation: 3230

TL;DR

When toMatchSnapshot matcher is used toJSON is called on the object passed to expect. It is a part of some kind of chain of checks that may have a neglectable, for a vast majority of real cases, performance impact. I would suggest calling toJSON just to follow "explicit is better than implicit" principle and the fact that the inner behavior is subject to change.

Explanation

The matcher uses SnapshotStates match method that serializes object. serialize just calls pretty-format with a set of plugins.

This is the entry point of pretty-format, I've added comments there:

function prettyFormat(
  val: unknown,
  options?: PrettyFormat.OptionsReceived,
): string {
  if (options) {
    validateOptions(options);
    if (options.plugins) {
      const plugin = findPlugin(options.plugins, val);
      /* When val is a result of `toJSON` call 
         it has a $$typeof: Symbol.for('react.test.json') field so  
         it is handled by ReactTestComponent plugin in this point */
      if (plugin !== null) {
        return printPlugin(plugin, val, getConfig(options), '', 0, []);
      }
    }
  }

  // When `toJSON` is not called `printBasicValue` is invoked but is it pointless
  const basicResult = printBasicValue(
    val,
    getPrintFunctionName(options),
    getEscapeRegex(options),
    getEscapeString(options),
  );
  if (basicResult !== null) {
    return basicResult;
  }

  return printComplexValue(val, getConfig(options), '', 0, []);
}

printBasicValue simply performs a set of checks, all of them fail for an object, so the printComplexValue takes its turn. Here is a fragment where .toJSON() is called.

  if (
    config.callToJSON &&
    !hitMaxDepth &&
    val.toJSON &&
    typeof val.toJSON === 'function' &&
    !hasCalledToJSON
  ) {
    return printer(val.toJSON(), config, indentation, depth, refs, true);
  }

printer finds a plugin and invokes it at the very beggining so it does not matter.

  const plugin = findPlugin(config.plugins, val);
  if (plugin !== null) {
    return printPlugin(plugin, val, config, indentation, depth, refs);
  }

You can notice that calling toJSON may be disabled by setting a config option or by some other conditions that I believe do not matter for the case of snapshot, but still.

Upvotes: 5

Related Questions