DEV Community

Cover image for 5 Tips to make testing more productive in React
Aki Rautio
Aki Rautio

Posted on • Updated on • Originally published at akirautio.com

5 Tips to make testing more productive in React

There is no such thing as "no tests" when talking about productive software development. The code will be tested all the time by the customers. You can either rely on them or test it yourself.

The fastest way to test your React project is to use automated testing. Here are five tips about how to make it productive:

1. Find a balance between different test types

React applications can be tested in many different ways. There are unit tests, functional tests, integration test, and end-to-end tests. Every type of test has its use case so using them together creates the best result.

On the other hand, all these test types overlap with each other. The larger end-to-end tests many times check the same things as integration and unit tests. Since lower level tests, like unit tests, are fast to do and run, it's advisable to use them where possible, and keep the more complex testing methods for use cases that are impossible to do with unit tests.

// Example tests for the Preview component can be found from Tip 2
const Preview = ({ text }) => text.lenght > 0 && (
 <div data-testid='preview'>{text}</div>
)


// Example tests for the Data component can be found from Tip 3
const Data = () => {
  const [text, setText] = React.useState()
  const setPreviewText = (value) => setText(`[${value}]`)
  return {
    <div>
      <Preview text={text} />
      <input 
        data-testid='input-text'
        name='input-text'
        value={text} 
        onChange={({ target }) => setPreviewText(target.value)}
      />
    </div>
  }
}

Enter fullscreen mode Exit fullscreen mode

Here we have two components that should be tested. Preview-component is stateless so we only need to unit test relevant inputs and outputs.

Data-component testing is a bit more complicated because logic is inside the component. Therefore we need functional testing to check the logic. We should also do integrations tests to see that Preview works correctly inside the Data-component. Though integration tests only need tests relevant interactions from Data component because we have already unit tested Preview-components inputs.

2. Make the application structure to be testable

Well structured code makes testing a lot easier and therefore makes the process faster and more fun. In React the stateless functional components are easiest to test since they have only one output per input setup. This creates an incentive to keep the component stateless if possible.

const Preview = ({ text }) => text.lenght > 0 && (
 <div data-testid='preview'>{text}</div>
)

describe('Preview', () => {
  it('should handle filled text', () => {
    expect(Preview({ text: 'test' }).to.be.equal(<div>test</div>)
  })

  it('should handle empty text', () => {
    expect(Preview({ text: '' }).to.be.equal(undefined)
  })

  it('should handle without text', () => {
    expect(Preview().to.be.equal(undefined) // Note: this will fail :)
  })
})
Enter fullscreen mode Exit fullscreen mode

Stateful components usually need tools to change internal state through inputs or by other means which add complexity. Good tools usually help out a lot in here but it takes a longer time to figure out needed tests. If a component has some internal functions which do not state dependent, a good practice is to test them separately. This reduces the number of tests needed to be handled in the stateful component.

// Example tests for Data component can be found from the Tip 3
const setPreviewText = (fn, value) => fn(`[${value}]`)
const Data = () => {
  const [text, setText] = React.useState()
  return {
    <div>
      <Preview text={text} />
      <input 
        data-testid='input-text'
        name='input-text'
        value={text} 
        onChange={({ target }) => setPreviewText(setText, target.value)}
      />
    </div>
  }
}

Enter fullscreen mode Exit fullscreen mode

3. Use correct tools

React has a lot of tools to ease up the testing. Probably the most used nowadays are:

Using the tools which ease up the complex tasks (especially with stateful components), makes testing a lot much faster so it's good to use some time to understand the difference between the tools.

For example with react testing library the Data component can be tested following way:

import { render } from '@testing-library/react'
const setPreviewText = (fn, value) => fn(`[${value}]`)
const Data = () => {
  const [text, setText] = React.useState()
  return {
    <div>
      <Preview text={text} />
      <input 
        data-testid='input-text'
        name='input-text'
        value={text} 
        onChange={({ target }) => setPreviewText(setText, target.value)}
      />
    </div>
  }
}

describe('Data', () => {
  it('should change preview text when input is changing', () => {
    const dataComponent = render(<Data />)
    const input = dataComponent.getByTestId('input-text')
    fireEvent.change(input, { target: { value: 'test' } })
    expect(dataComponent.getByTestId('preview')).toBeEqual('[test]')
  })
})

Enter fullscreen mode Exit fullscreen mode

4. Combine visual with code related tests

Visual and snapshot testing are wonderful tools to ensure that component keeps their designed look in every situation. The idea of these tools is simple. At first, the test will create a snapshot from the code or by creating an image. When the same component is tested again, the test tool will compare the current situation with the snapshotted version and notifies about the changes. If changes where done on purpose, the snapshots are updated, otherwise the code needs to be checked.

Visual tests work especially well in React since most of the components produce a small part of the interface. They also speed up the testing tremendously because they eliminate the need to write checks whether DOM or styles look as they should.

On unit test level, Jest supports snapshots out of the box with react-test-renderer.

import renderer from 'react-test-renderer';

const Preview = ({ text }) => text.lenght > 0 && (
 <div id='preview'>{text}</div>
)

it('renders correctly', () => {
  expect(
    renderer
    .create(<Preview text='test'/>)
    .toJSON()
  ).toMatchSnapshot()
})
Enter fullscreen mode Exit fullscreen mode

If you are using Storybook to develop the components, I would highly recommend storyshoots. It's a fantastic way to catch unwanted changes before they even go to the codebase.

5. Automatically run tests either on commit on a pull request.

One of the easiest ways to keep high code quality is to run tests on every commit (or pull request). When there are only a fairly small amount of changes done, finding the issue behind the breaking test is faster and bugs stay alive the least amount of time.

If the project is running GIT as version control, there is a possibility to include addition commands (hooks) when running commits or pushes. Husky makes this process even easier, so I can highly recommend the tool.

Since end-to-end tests tend last almost forever, it would make sense to run these on a cycle (like once in an hour or a day depending on how often the codebase is changing).


How are you making your test to be effective? Let me know in the comments :)

Top comments (0)