Let’s step up our testing game with two useful libraries that lend themselves excellently to a TDD approach.

Setting up

Whenever I want to try out something React-related, I use the library create-react-app. It gives you a ready-to-work-with basic React application with no configuration needed. Recent versions also come bundled with React Testing Library, so if you use the latest create-react-app, you can start using React Testing Library straight away. If not — install @testing-library/react and @testing-library/jest-dom in your existing React application.

I am going to implement the following functionality: a simple recipe list with a search function. It will look something like this to a user in a mobile browser:

Image for post

Image credits: Burger Photo by Robin Stickel on Unsplash, French Toast Photo by Joseph Gonzalez on Unsplash, Salmon Photo by Casey Lee on Unsplash

Starting with a failing test

I want to use a TDD approach, so let’s start with a failing test. At this point, there is no component yet, so of course whatever test we write is going to fail. But let’s start small; I want a component named ‘Recipes’ that renders the expected heading text. Here is my test for that expectation:

	import React from 'react';
	import { render, screen } from '@testing-library/react';

	test('renders the heading', () => {
	  render(<Recipes />);

	  expect(screen.getByRole('heading')).toHaveTextContent('Recipe Finder');
	});

React testing library exports a render method, which will render a component and all of its child components. It also exports a screen object, holding a number of queries we can use to select different elements in our rendered component (and its child components too). The **getByRole **query lets me select the heading element and make an assertion on its text content.

Why select the heading element by its role and not for example a CSS class? The guiding principle of React Testing Library is “The more your tests resemble the way your software is used, the more confidence they can give you.” Therefore, we want to write our tests as close as possible to how the ultimate tester — the end user — will be using the application. Users don’t see CSS classes or data attributes; they interact with text, label text and semantic elements and roles. Using queries such as getByRole also encourages us to write accessible code, since these selectors are available to everyone, including users of screen readers.

Making the test pass

Our first test fails as expected.** Recipes is not defined.** But this is the first step in TDD — a failing test. Now, let’s make it pass by writing the simplest possible component with the correct heading and then importing it in our test file. Now, let’s re-run the test. It passes!

	import React from 'react';

	const Recipes = () => {
	  return (
	    <div>
	      <h1>Recipe Finder</h1>
	    </div>
	  )
	};

	export default Recipes;

Using further queries, we can make similar expectations for the input element and the “Find” button. The button also has a role, but for the input field, I will use the getByPlaceholderText query, since that is probably the closest query to how the user would find it on the page.

Start with a failing test…

	import React from 'react';
	import { render, screen } from '@testing-library/react';
	import Recipes from './Recipes';

	test('renders the heading, input field and button', () => {
	  render(<Recipes />);

	  expect(screen.getByRole('heading')).toHaveTextContent('Recipe Finder');
	  expect(screen.getByPlaceholderText('Enter an ingredient to find recipes...'))
	    .toBeInTheDocument();
	  expect(screen.getByRole('button')).toHaveTextContent('Find');
	});

… and implement the changes necessary to make it pass:

	import React from 'react';

	const Recipes = () => {
	  return (
	    <div>
	      <h1>Recipe Finder</h1>
	      <form>
	        <input 
	          type="text" 
	          name="ingredient"
	          placeholder="Enter an ingredient to find recipes..." 
	        />
	        <button type="submit">Find</button>
	      </form>
	    </div>
	  )
	};

	export default Recipes;

This way, we know what we expect from our code and more importantly — we will know if we break any functionality if the previously passing tests suddenly fail.

An important step in TDD is the refactor step, where we improve our code to for example make it easier to read, become more efficient and remove any duplication. The test should still pass after we refactor.

Setting up our mocks

When the application first renders, I want to display a list of all my recipes, just like in the visual design above. This requires some kind of communication with an API. We are going to use Mock Service Worker to mock the HTTP-requests, so that we can control the response data. Install Mock Service Worker with npm like this:

npm install msw --save-dev

With Mock Service Worker, we are not mocking a specific module (unlike if we were to use Jest.mock), which means that it makes no difference if I use fetch or a third-party library such as axios to get the data. This makes it incredibly flexible. Let’s add the following imports to our test file:

import { rest } from 'msw';
import { setupServer } from 'msw/node';

Here is how I set up mocking a call to the recipe list endpoint:

	import React from 'react';
	import { render, screen } from '@testing-library/react';
	import Recipes from './Recipes';
	import { rest } from 'msw';
	import { setupServer } from 'msw/node';

	const allRecipes = [
	  { id: 1, title: 'Burger' }, 
	  { id: 2, title: 'French toast' }, 
	  { id: 3, title: 'Salmon' }
	];

	const server = setupServer(
	  rest.get('/api/recipes', (req, res, ctx) => {
	    return res(ctx.json({ recipes: allRecipes }));
	  })
	);

	beforeAll(() => server.listen());

afterAll(() => server.close());

If you have worked with NodeJS and Express, the syntax looks very familiar. GET requests to ‘/api/recipes’ will respond with JSON containing the allRecipes array, just like a real API would. These two lines make sure the server starts listening (intercepting) before the tests run and closes its connection when the tests in this file have finished running:

beforeAll(() => server.listen());
afterAll(() => server.close());

#tdd #react-testing-library #react #test-driven-development #testing

Test Driven Development (TDD) with React Testing Library & Mock Service Worker
11.65 GEEK