Writing the First Test & Component
Alex Edwards
Posted on June 12, 2020
Now that Typescript, Jest, and Cypress are set up and ready to go, let's run the site:
npm run setup
With the single command, npm installs everything runs the validate script which runs both Jest and Cypress one after another.
Nothing should happen at this stage, as there are no tests written. From a Test-Driven perspective, tests establish both how to interact with the module and what the outcome will be. Typescript is excellent for that documentation during development, but it won't do anything for runtime.
My site's going to feature a layout component that will wrap the site so that through page-change, elements in layout
won't flicker or repaint. In my layout, there are SEO components; thank's to react-helmet
, as well as a Header,
and Footer
component.
My Header will render with a title
, and the main navigation for the website. That information will be passed in by Gatsby's graphql
during Build time; so I know I need to mock the data passed into the component. Another aspect that would be useful to know is the current location path so that any styles are adjusted to show the current page.
Mocking Graphql with Jest .spyOn
Per gatsby's excellent documentation, it's as easy as statically declaring what information is present. GraphiQL is your friend here, as the data passed will be well structured and fields populated with the correct types. It will end up looking something like this:
const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery');
useStaticQuery.mockImplementation(() => ({
contentfulSiteMetadata: {
description: 'Hello-Title'
},
allContentfulPage: {
edges: [
{
node: {
title: 'about',
meta: {
slug: "about"
}
}
},
{
node: {
title: 'contact',
meta: {
slug: "contact"
}
}
}
]
}
}));
By doing this, I don't need to worry about dependency injection. Instead, Jest will monitor the component, and use its mock data for useStaticQuery
.
Queries and other APIs return generated data, so mock the returned value but not the implementation.
Ensure required elements are present
So writing the tests are now as easy as ensuring that the purpose of the Header
component renders the right stuff:
test('It has a CTA', () => {
const { getByRole } = render(<Header location={mockLocation()} />)
const button = getByRole('button')
expect(button).toContain(/contact/i)
})
or the better way of doing things:
test('It has a CTA Button', () => {
render(<Header location={mockLocation()} />)
screen.getByRole('button', { name: /contact/i })
})
Kent C Dodds' Pro Testing-Javascript course taught the method of testing how it's interacted with rather than it's implementation.
Writing the Component
Now that I've written the test, using it as a blueprint, we can get going with the component to make the test pass. In this case, laid out to use useStaticQuery
and return a header element with a title present, a navigation element with links and a Call-To-Action button.
export default function Header () {
return (
<header>
<p>Alex Edwards | {data.description}</p>
<nav>
{data.allContentfulPage.edges.map(page => {
if (page.node.title === `contact`) return (
<button
name={page.node.title}
key={page.node.meta.slug} >
{page.node.title}
</button>
)
{...}
}
</nav>
</header>
)
}
Rerunning the test will pass this time because our component is returning the required elements. Now it's safe to add, modify, or refactor the code and keep Jest in watch mode to ensure all changes don't affect the requirements of this module.
Awesome tests to include would be using Jest-Axe to ensure all a11y requirements are passing, as well as Jest-In-Case to test through multiple props or data-values to ensure that different use cases return the right values, including error handling.
Other modules, such as my footer and other singular components, have similar tests associated with them. Elements like my Layout wrapper will have a different approach, called integration tests
. The mentality is to ensure that everything works well together. Does the data passed from Layout
to Header
and Footer
render successfully?
Later, the confidence gained from these tests will allow quicker development for pages, templates and the rest.
This post is an ongoing series along with my site development. All current process on my website and in the open-source repo is open for review. As I publish new features (Posts and Projects templates, changes in style), I'll continue to add to this series as a reflection of what I've learnt, and what reflections I have on the process.
Posted on June 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.