Prevent Visual Regressions

If you compose your React applications as we have been doing so far, there’s a clear separation between parts of the application that handle logic and parts that just concern themselves with the UI. Checking that the UI matches expectations is tedious. Even if a component outputs different HTML elements, the visual result might turn out to be identical, so tests that check the generated HTML might break even though the application still displays the expected UI. Although these sort of tests tend to break without good reason, you might not want to skip them completely, as they still can alert you that something is wrong. We’ll compromise and use a solution that will let you write and update tests very fast, so that you still have some sort of safety net, without spending too much time keeping the tests up to date.

Instead of selecting parts of the UI and comparing them with the expected result by writing code by hand, we can use snapshot tests. Snapshot tests compare a rendered element to a JSON representation of the same element. The first time a snapshot test runs, it’s not much of a test, as it just generates the JSON representation. On subsequent test runs, snapshot tests generate a new snapshot and fail whenever they detect a difference with the original snapshot. To see how this works in practice, let’s create a snapshot test for the Counter component.

First, we need to install an additional development dependency. To be able to render component snapshots, install react-test-renderer:

 $ ​​npm​​ ​​i​​ ​​--save-dev​​ ​​react-test-renderer

In the __tests__ directory, create a new file named Counter.test.js and import the test renderer, React, and the Counter component:

 import​ React from ​'react'​;
 import​ renderer from ​'react-test-renderer'​;
 import​ Counter from ​'../Counter'​;

Then create a new test suite with one test function. As before, describe introduces the test suite, and it sets up a single test.

 describe(​'A counter'​, () => {
  it(​'Displays the count and label'​, () => {
  });
 });

Compare the render output with a snapshot in the body of the it callback. Create a Counter element, then pass it to renderer.create. Convert the result to JSON, then use expect and toMatchSnapshot to assert that it matches the stored snapshot:

 describe(​'A counter'​, () => {
  it(​'Displays the count and label'​, () => {
»const​ counter = renderer.create(<Counter legend=​"Count"​ count={34} ​/>​​)​​;
» expect(counter.toJSON()).toMatchSnapshot();
 
  });
 });

Jest generates the snapshot during the first run, and by convention, a snapshot test always passes the first time. Right now, there’s no snapshot stored yet. To generate the initial snapshot, run the tests:

 $ ​​npm​​ ​​test

When the test completes, Jest informs you it has created one new snapshot:

 Snapshot Summary
 › 1 snapshot written in 1 test suite.
 
 Test Suites: 2 passed, 2 total
 Tests: 3 passed, 3 total
 Snapshots: 1 added, 1 total
 Time: 1.37s
 Ran all test suites.

If you run the tests again, Jest uses the snapshot it generates in the first test run as a reference. Snapshot tests pass if the result matches the snapshot, so snapshot tests are accurate only if the component rendered correctly when you generated the snapshot. If you need someone else to run the same tests as you, you need to share the __tests__/__snapshots__ directory with them, as Jest stores the snapshots in there.

Let’s demonstrate what happens when the component does not match the snapshot. Suppose you want to change the markup of the Counter component to make it more accessible. Open Counter.js and enclose the description in a <label> element and the count in an <output> element:

 function​ Counter({ count }) {
 return​ (
» <p className=​"mb2"​>
» <label htmlFor=​"count"​>Word count: </label>
» <output id=​"count"​>
»{​count​}
» </output>
» </p>
  );
 }

htmlFor on <label> points to id on <output>, making the relationship between <label> and <output> explicit. Let’s see how the snapshot tests report the change. Run the tests again:

 $ ​​npm​​ ​​test

The snapshot test fails, since the output changed from when you recorded the snapshot. To help you pinpoint the change causing the failure, Jest prints a diff showing where the new version differs from the old one.

 FAIL __tests__/Counter.test.js
  ● A counter › Displays the count and label
 
  expect(value).toMatchSnapshot()
 
  Received value does not match stored snapshot 1.
 
  - Snapshot
  + Received
 
  @@ -1,6 +1,11 @@
  <p
  className="text-muted">
  - Count
  -
  - 34
  + <label
  + htmlFor="count">
  + Count
  + </label>
  + <output
  + id="count">
  + 34
  + </output>
  </p>

Whenever a snapshot test fails, you must examine the diff and decide whether the change was intentional. If the change happened by mistake, fix the problem in the application code until the snapshot test passes again. If you wanted the change, update the snapshot to use the current version as the reference from now on by running the tests with the -u flag:

 $ ​​npm​​ ​​test​​ ​​--​​ ​​-u

Jest reports that it has updated one snapshot, and all tests pass again:

  PASS __tests__/Counter.test.js
  PASS __tests__/count.test.js
 
 Snapshot Summary
  › 1 snapshot updated in 1 test suite.

You will frequently want to extract separate components from a component that has become very large. In that case, snapshots help you confirm that you preserve the original output. The inconvenient part is that the test fails when any change alters the component’s appearance, but that tends to occur in all tests that check a UI. Try to keep your components small, but test all props.

You want to maximize your tests’ efficiency, as well as write the least amount of test code to find the most amount of bugs. If the tests break all the time because of implementation details, then they’re not supplying very valuable information, because whether they break or not does not tell you that you made a mistake.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.141.220