3

JavaScript testing #10. Advanced mocking with Jest and React Testing Library

 2 years ago
source link: https://wanago.io/2022/04/18/advanced-mocking-jest-react-testing-library/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
JavaScript testing #10. Advanced mocking with Jest and React Testing Library

React Testing

April 18, 2022
This entry is part 10 of 10 in the JavaScript testing tutorial

In the fourth part of this series, we’ve learned the basics of mocking API calls. However, there are often situations where we would like to test various more demanding cases. So, in this article, we implement more advanced examples on how to mock with Jest.

Creating a simple React component

Let’s start by creating a straightforward React component that renders a list of posts.

Posts.tsx
import React from 'react';
import usePostsLoading from './usePostsLoading';
const Posts = () => {
  const { isLoading, posts, hasFailed } = usePostsLoading();
  if (hasFailed) {
    return <div data-testid="posts-error">Something went wrong</div>;
  if (isLoading) {
    return <div data-testid="posts-loading">Loading...</div>;
  if (posts?.length === 0) {
    return <div data-testid="posts-empty">There are no posts to display</div>;
  return (
    <div data-testid="posts-container">
      {posts?.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </div>
    </div>
export default Posts;

The logic of storing the data in the state resides in the usePostsLoading hook.

usePostsLoading.tsx
import { useEffect, useState } from 'react';
import fetchPosts from './fetchPosts';
interface Post {
  id: number;
  title: string;
  body: string;
function usePostLoading() {
  const [isLoading, setIsLoading] = useState(false);
  const [hasFailed, setHasFailed] = useState(false);
  const [posts, setPosts] = useState<Post[] | null>(null);
  useEffect(() => {
    setIsLoading(true);
    setHasFailed(false);
    fetchPosts()
      .then((fetchedPosts: Post[]) => {
        setPosts(fetchedPosts);
      .catch(() => {
        setHasFailed(true);
      .finally(() => {
        setIsLoading(false);
  }, []);
  return {
    posts,
    isLoading,
    hasFailed,
export default usePostLoading;

The fetchPosts function takes care of fetching the data. It is the one we will mock in this article.

fetchPosts.tsx
function fetchPosts() {
  return fetch('https://jsonplaceholder.typicode.com/posts').then(
    (postsResponse) => {
      if (postsResponse.ok) {
        return postsResponse.json();
      return Promise.reject();
export default fetchPosts;

Mocking a function

In the fourth part of this series, we’ve defined mocks in the __mocks__ directory. Instead, we can mock a function at the top of our test file.

Mocking a named export

Let’s rewrite our fetchPosts.tsx file to use a named export for a moment.

fetchPosts.tsx
function fetchPosts() {
  return fetch('https://jsonplaceholder.typicode.com/posts').then(
    (postsResponse) => {
      if (postsResponse.ok) {
        return postsResponse.json();
      return Promise.reject();
export { fetchPosts };

With the above approach, it is straightforward to mock a module.

jest.mock('./fetchPosts', () => ({
  fetchPosts: jest.fn().mockReturnValue(Promise.resolve([])),

Above, we use jest.mock() function that creates a mock function. For example, we can use it to change the value that a function returns. In our case, we force the fetchPosts function to return a promise that resolves to an empty array.

Mocking a default export

Our original fetchPosts.tsx file uses a default export. Therefore, we need to modify our mock a bit.

jest.mock('./fetchPosts', () => ({
  __esModule: true,
  default: jest.fn().mockReturnValue(Promise.resolve([])),

We can simplify the above mock a little by using the mockResolvedValue method.

Posts.test.tsx
import { render } from '@testing-library/react';
import React from 'react';
import Posts from './Posts';
jest.mock('./fetchPosts', () => ({
  __esModule: true,
  default: jest.fn().mockResolvedValue([]),
describe('The Posts component', () => {
  describe('when the component fetches an empty array of posts', () => {
    it('should render the no posts indicator', async () => {
      const posts = render(<Posts />);
      await posts.findByTestId('posts-empty');

The fetchPosts returns a promise that resolves to an empty array thanks to the above. Because of that, the Posts component does not make an actual HTTP request.

A crucial thing to notice in our test is that our fetchPosts function is still asynchronous. Since we use await posts.findByTestId('posts-empty'), our test waits up to 1000ms for the <div data-testid="posts-empty" /> to appear. If the React Testing Library does not find the element during that time, it throws an error, and our test fails.

Mocking a function differently per test

Above, we create a mocked version of the fetchPosts function that always returns the same value. Instead, we might want to mock a function differently per test. Let’s start by using the jest.mock() function on top of our test.

jest.mock('./fetchPosts', () => ({
  __esModule: true,
  default: jest.fn()

Thanks to doing the above, our fetchPosts function is now replaced with a mock function. The crucial thing to acknowledge is that when we import fetchPosts in usePostsLoading.tsx or post.test.tsx, we import an instance of a mock function. So we can use this fact and interact with our mock through the test to change what it returns in usePostsLoading.tsx.

Posts.test.tsx
import { render } from '@testing-library/react';
import React from 'react';
import Posts from './Posts';
import fetchPosts from './fetchPosts';
jest.mock('./fetchPosts', () => ({
  __esModule: true,
  default: jest.fn(),
describe('The Posts component', () => {
  describe('when the component fetches an empty array of posts', () => {
    beforeEach(() => {
      (fetchPosts as jest.Mock).mockResolvedValue([]);
    it('should render the no posts indicator', async () => {
      const posts = render(<Posts />);
      await posts.findByTestId('posts-empty');
  describe('when the posts fetching fails', () => {
    beforeEach(() => {
      (fetchPosts as jest.Mock).mockRejectedValue(null);
    it('should render the error indicator', async () => {
      const posts = render(<Posts />);
      await posts.findByTestId('posts-error');

Since the fetchPosts function that we import on top of our test now has a value of jest.fn(), we can call mockResolvedValue and mockRejectedValue on it. We can achieve a different mocked value per test by manipulating the fetchPosts mock before each test.

Mocking a React component

When writing tests with Enzyme, we can choose to either render a component with all of its children or perform a shallow render. In contrast, we don’t have this option with React Testing Library.

The idea behind React Testing Library is to write tests that resemble the way the users interact with our application. Because of that, mocking React components is discouraged. Even though that’s the case, we sometimes might want to avoid testing some components as a whole. The above might happen when using third-party libraries, for example.

DataViewer.tsx
import React, { FunctionComponent } from 'react';
import ReactJson from 'react-json-view';
interface Props {
  data: unknown;
const DataViewer: FunctionComponent<Props> = ({ data }) => {
  if (data !== null && typeof data === 'object') {
    return <ReactJson src={data} />;
  return <p>{data.toString()}</p>;
export default DataViewer;

In our test, let’s find out if the DataViewer renders the ReactJson component when provided with a dictionary.

DataViewer.test.tsx
import DataViewer from './DataViewer';
import { render } from '@testing-library/react';
jest.mock('react-json-view', () => ({
  __esModule: true,
  default: () => <div data-testid="react-json-view" />,
describe('The DataViewer component', () => {
  describe('when provided with a dictionary', () => {
    it('should render a ReactJson component', async () => {
      const dataViewer = render(<DataViewer data={{ key: 'value' }} />);
      await dataViewer.findByTestId('react-json-view');
  describe('when provided with a string', () => {
    it('should render a paragraph with the provided string', async () => {
      const text = 'value';
      const dataViewer = render(<DataViewer data={text} />);
      await dataViewer.findByText(text, { selector: 'p' });

Without mocking the react-json-view library above, we wouldn’t have a straightforward way of checking whether our component rendered it when using React Testing Library.

Mocking a module partially

So far, we’ve constantly mocked a whole module when using jest.mock(). We might not want to do that in every case. Let’s imagine having the following file:

utilities.tsx
function sum(firstNumber: number, secondNumber: number) {
  return firstNumber + secondNumber;
function subtract(firstNumber: number, secondNumber: number) {
  return firstNumber - secondNumber;
export { sum, subtract }

If we want to mock only the sum function, we can pair jest.mock() with jest.requireActual(). When we call jest.requireActual(), we can retrieve the original content of a module even if we mocked it.

jest.mock('./utilities', () => ({
  ...jest.requireActual('./utilities'),
  sum: jest.fn(),

Summary

In this article, we’ve gone through various use-cases of mocking functions. This included real-life situations such as mocking a function differently per test, mocking a default export, or mocking a React component. We’ve also learned how to mock a module partially. All of the above provides a cheat sheet that might come in handy in various cases.

Series Navigation<< JavaScript testing #9. Replacing Enzyme with React Testing Library

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK