How to unit test your Gatsby blog with React Testing Library

Emma Goto 🍙 - Oct 15 '20 - - Dev Community

Writing unit tests will keep your Gatsby blog bug-free, and leads to more maintainable code. This post covers how you can use Jest and React Testing Library to cover some of your Gatsby blog’s most essential features.

Why add unit tests?

When you’re hacking away on a side project, writing unit tests isn’t fun and can be easily missed. I’ll admit I’ve often skipped writing unit tests for side projects - but I always end up regretting it later. Without unit tests, adding a bug fix or new feature becomes a lot scarier because you don’t know if you’re going to break something else in the process.

Writing tests as you go will immediately boost the quality of your code too, as unit tests force you to think a bit harder on the purpose of each function or component. It might be a struggle at first but the more you’ll do it, the easier it gets.

(Also seeing all those green ticks after you run your tests can be satisfying!)

What should you test on your Gatsby blog?

You can create tests that make sure components are rendering on the page that you would expect to render. This can be useful to make sure your pages aren't completely broken! It's also good to test any logic that you've added to your blog.

In this post we’ll be using some of the unit tests I added to my blog as an example, and covering the following areas:

  • Testing that my post’s dates render as expected
  • Testing that my SEO component is outputting the correct meta tags
  • Testing that my home page renders links to 3 of my recent posts

Installing Jest and React Testing Library

Getting started with unit testing is a bit more of a complex setup process for Gatsby than it would be for your regular React app. Luckily Gatsby provides some great documentation on unit testing, so I would recommend following the steps on there to install Jest.

Next, you’ll also need to follow Gatsby’s instructions on testing React components so that you can install React Testing Library.

Why do we need both Jest and React Testing Library?

Jest is the framework that runs the tests for you.

Jest lets you do things like describe your suite of tests with describe and test, as well as make assertions using expect:

describe('Test name', () => {
    test('should be true', () => {
        expect(true).toBe(true);
    });
});
Enter fullscreen mode Exit fullscreen mode

Where React Testing Library comes into play is that it allows you to render your React apps and components, and then select certain parts of them to assert on:

describe('Test name', () => {
    test('should be true', () => {
        render(<Component />);
        const text = screen.findByText('hello');
        expect(text).toBeTruthy();
    });
});
Enter fullscreen mode Exit fullscreen mode

Testing that my dates are rendered correctly

For posts published in the 2020, my blog will only render the day and month that the post was published (e.g. 16 Sept). For posts published last year, I will render the year as well (e.g. 16 Sept 2019).

Here is an example of the sort of unit test I would write for this scenario:

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

describe('PostSummary component', () => {
    test('should render year if post is from 2019', () => {
        const post = {
            name: 'Post title',
            date: '16 Sept 2019',
        };
        render(<PostSummary post={post} />);
        expect(screen.getByText('16 Sept 2019')).toBeTruthy();
    });
});
Enter fullscreen mode Exit fullscreen mode

In the above unit test we:

  1. Use RTL’s render function. This will render our React component and make it available to query on via the screen object.
  2. Use the getByText query to assert that the text that we expect to be present is there.

As well as getByText, there are a number of other queries you can use depending on the situation. React Testing Library provides a useful guide for which query you should use.

Pro-tip: If you’re running into issues with your unit tests, you can add a console.log(screen.debug()) to your tests. This lets you double-check that your test is rendering what you’re expecting it to be rendering.

As well as testing the scenario for a post from 2019, I’ve also written a unit test for if a post was written in the current year.

Testing your SEO component

If you’ve created your Gatsby blog using one of the default starter templates, chances are you’ll have a SEO component that uses react-helmet to generate your site’s meta tags. This contains things like the title of the page and what data your post would show if it was linked on Twitter or other social media sites.

If you’re interested in learning more about meta tags, check out my recent post on adding meta tags to your Gatsby blog

Mocking Gatsby’s useStaticQuery

The first thing thing my SEO component does is get some of my site’s metadata with Gatsby’s useStaticQuery:

// src/components/seo/index.js
const { site } = useStaticQuery(
    graphql`
        query {
            site {
                siteMetadata {
                    title
                    description
                    author
                    siteUrl
                }
            }
        }
    `,
);
Enter fullscreen mode Exit fullscreen mode

This data isn’t accessible in our unit test, so we’re going to need to mock what useStaticQuery returns. We can do this with Jest's mockReturnValue:

// src/components/seo/test.js
describe('SEO component', () => {
    beforeAll(() => {
        useStaticQuery.mockReturnValue({ site: {
                siteMetadata: {
                    title: `Emma Goto`,
                    description: `Front-end development and side projects.`,
                    author: `Emma Goto`,
                    siteUrl: `https://www.emgoto.com`,
                },
            },
        });
    });

    test(...)
});
Enter fullscreen mode Exit fullscreen mode

We’re putting it inside a beforeAll hook which means this will get mocked once before all our tests run.

If you only ever use useStaticQuery to get your site's data, you could move this code to live inside of __mocks__/gatsby.js so you only need to mock it once across all of your test files.

Testing your meta tags with Helmet’s peek()

With meta tags, you won’t be able to query for it on the screen object like we did with our previous unit test. Instead, we’ll need to make use of a function that React Helmet provides called peek():

// src/pages/index.test.js
import { render } from '@testing-library/react';
import Helmet from 'react-helmet';

test('should render correct meta data for home page', () => {
    render(<SEO title={postTitle} />);
    const helmet = Helmet.peek();
Enter fullscreen mode Exit fullscreen mode

This gives us an object containing all the meta tags created by our Helmet component. We can now write tests to assert that specific values are present:

expect(helmet.title).toBe(siteTitle);

expect(helmet.metaTags).toEqual(
    expect.arrayContaining([
        {
            property: 'og:title',
            content: siteTitle,
        },
    ]),
);
Enter fullscreen mode Exit fullscreen mode

You can see the full set of tests for my SEO component over on Github.

Testing that my home page renders three recent posts

My site’s home page renders my three most recent blog posts. It gets this data using a GraphQL page query, which will be passed in as a prop to my component:

// src/pages/index.js
const IndexPage = ({ data }) => (
    <>
        // renders the posts using the given data
    </>
);

export const pageQuery = graphql`
    query {
        allMdx {
            nodes {
                frontmatter {
                    title
                    date(formatString: "DD MMMM YYYY")
                }
            }
        }
    }
`;

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

Mocking the data

Since you can’t run the page query in a unit test, you’ll need to create a mock data object to pass into your component:

const data = {
    nodes: [
        {
            frontmatter: {
                title: "Post #1", date: "01 Jan 2020"
            },
        },
    ],
};

test('should render three most recent posts', async () => {
    render(<IndexPage data={data} />
Enter fullscreen mode Exit fullscreen mode

This approach is useful if you wanted to test a specific scenario e.g. what would happen if two posts were published on the same day.

However, if you broke your page query at some point in the future, your unit test would still pass.

Use real data with gatsby-plugin-testing

To use up-to-date data from your GraphQL page query, you can make use of gatsby-plugin-testing:

import { getPageQueryData } from 'gatsby-plugin-testing';

test('should render three most recent posts', async () => {
    const data = await getPageQueryData('index');
    render(<IndexPage data={data} />);
Enter fullscreen mode Exit fullscreen mode

This plugin will give you real data, identical to what your GraphQL query returns. This means that if you modify your GraphQL query in any way, the unit test will also use the new data from this query.

The trade-off with this approach is that since this is real data, you can't do things like assert that a specific post title will be available on your home page (if you are showing your most recent posts). If you did, the unit test would break as soon as you added more blog posts.

Finding my blog post links

Since each of my recent posts are links, one way we could find the posts is by using the getAllByRole query:

const links = screen.getAllByRole('link');
Enter fullscreen mode Exit fullscreen mode

This will return a list of all the links on the page. In my case however, my home page has a lot of other links so this isn’t too useful.

Instead, I decided to add a data-testid prop to all my blog post links:

// src/components/summaries/index.js

const PostSummary = () => <div data-testid="summary">...</div>
Enter fullscreen mode Exit fullscreen mode

It’s recommended that you use data-testid instead of relying on a CSS class name or some other implementation detail, as we don’t want our unit tests to break if we edit the CSS, for example.

Now in my unit test, I can find all elements that match the given test ID, and assert that there are three of them:

const posts = screen.getAllByTestId('summary');
expect(posts.length).toEqual(3);
Enter fullscreen mode Exit fullscreen mode

This test is fairly simple and I will admit that it’s not going to pick up on all the possible edge cases or bugs that could occur.

However, I’ve often broken certain pages of my website with small typos or changes so even a simple test like this one will let you know if anything is majorly broken, and so I still think it has a lot of use and is worth writing!

Conclusion

I’m guilty of skipping unit tests for my side projects a lot of the time, but by writing them for my Gatsby blog I think I’ve come out with a codebase that’s a tiny bit cleaner, and I have a lot more confidence to keep making changes to it in the future.

I hope this post helps you in unit testing your Gatsby blog or next side project.

Thanks for reading!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player