September 22, 2019

NOTE: This is a cross-post from my newsletter. I publish each email one week after it’s sent. Subscribe to get more content like this earlier right in your inbox! 💌

Hi ! This is a guide/cheatsheet that I comeback to read when I want to write tests for a project.

I thought this might help other fellow developers so here you go 😁

Setup

Install jest, cypress and helper libraries

yarn add jest @testing-library/react @testing-library/jest-dom -D

Config

In this section we’ll configure Jest and Cypress

Jest

Let’s create a config file for Jest in the root directory:

module . exports = { testURL : 'https://example.com' , testPathIgnorePatterns : [ '/node_modules/' ] , setupTestFrameworkScriptFile : require . resolve ( './test/setup.js' ) , modulePaths : [ '<rootDir>/src' ] , moduleNameMapper : { '\\.css$' : '<rootDir>/test/module-mock.js' , '\\.svg$' : '<rootDir>/test/module-mock.js' , } , collectCoverageFrom : [ '**/src/**/*.js' ] , coverageThreshold : { global : { statements : 20 , branches : 20 , functions : 20 , lines : 20 , } , } , }

Now create a test folder in the root directory and create setup.js file inside it:

import '@testing-library/react/cleanup-after-each' import '@testing-library/jest-dom/extend-expect'

also create a module-mock.js in the same test folder :

module . exports = { }

Code coverage

In package.json add --coverage at the end of your test script:

{ ... "scripts" : { ... "test" : "jest --coverage" } }

Watch mode

When coding, use Jest in watch mode to get instant feedback about the tests related to the files you are changing. To use this feature, add a script to package.json and use it:

{ ... "scripts" : { ... "test:watch" : "jest --watch" } }

Cypress

Install cypress and helpers:

yarn add cypress @testing-library/cypress -D

then add a script to package.json to run cypress:

{ ... "scripts" : { ... "cy:open" : "cypress open" , "cy:run" : "cypress run" , } }

yarn cy:open

Cypress records videos and takes screenshots of the app while running tests. Let’s add the folders that Cypress uses for this to .gitignore

... cypress/videos cypress/screenshots

cypress.json

When running cypress open for the first time, it creates a bunch of files and folders inside a folder in the root dir called cypress . It also creates a file in the root dir called cypress.json . That’s the configuration file cypress uses.

Let’s add a baseUrl to use in our E2E test:

{ "baseUrl" : "http://localhost:3000" }

@testing-library/cypress

@testing-library/cypress adds some very handy commands to cypress, let’s configure it:

Go to <rootDir>/cypress/support , open index.js and add this line:

import '@testing-library/cypress/add-commands' ...

Test utils (helpers):

Have a test-utils file that exports a set of tools that are used specifically for the project you are testing.

Example:

Export a render method that takes care of adding styled-components ThemeProvider HOC:

import React from 'react' import { render as originalRender , wait , } from '@testing-library/react' const theme = { colors : { red : 'red' , } , } function render ( component , renderOptions ) { const utils = originalRender ( < ThemeProvider theme = { theme } > { component } </ ThemeProvider > , renderOptions ) return { ... utils , } } export { render }

Now in your tests, import render from this test-utils file instead of @testing-library/react

Unit test

Write a unit test when you want to test the functionality of ONE function/component:

import React from 'react' import { render } from '@testing-library/react' import Paragraph from '../paragraph' test ( 'renders the text given' , ( ) => { const { getByText } = render ( < Paragraph > Hello </ Paragraph > ) expect ( getByText ( /Hello/i ) ) . toBeInTheDocument ( ) } )

Integration test

Write an integration test when you want to test the functionality of several components working togather:

import React from 'react' import { MockedProvider } from '@apollo/react-testing' import wait from 'waait' import { fireEvent } from '@testing-library/react' import { render } from '../test-utils' import App , { LOGIN_MUTATION } from '../app' beforeEach ( ( ) => { window . localStorage . removeItem ( 'token' ) } ) test ( 'login as a user' , async ( ) => { const fakeUser = { id : 123 , username : 'fakeuser' } const fakeUserCredentials = { ... fakeUser , password : 'stupidpassword123' , } const token = 'thisisjustanexampleofatoken-youcanuseafakedatageneratorinstead' const loginMutationMock = jest . fn ( ) const loginMutationErrorMock = jest . fn ( ) const mocks = [ { request : { query : LOGIN_MUTATION , variables : { username : fakeUserCredentials . username , password : fakeUserCredentials . password , } , } , result : ( ) => { loginMutationMock ( ) return { data : { user : fakeUser , token : token } } } , error : ( ) => { loginMutationErrorMock ( ) } , } , ] const { getByTestId , getByText , getByLabelText } = render ( < MockedProvider mocks = { mocks } addTypename = { false } > < App /> </ MockedProvider > ) fireEvent . click ( getByText ( /login/i ) ) const usernameNode = getByLabelText ( /username/i ) const passwordNode = getByLabelText ( /password/i ) usernameNode . value = fakeUserCredentials . username passwordNode . value = fakeUserCredentials . password fireEvent . click ( getByText ( /sign in/i ) ) await wait ( 0 ) expect ( loginMutationMock ) . toHaveBeenCalledTimes ( 1 ) expect ( loginMutationErrorMock ) . not . toHaveBeenCalled ( ) expect ( window . localStorage . getItem ( 'token' ) ) . toBe ( token ) expect ( getByTestId ( 'username' ) . textContent ) . toEqual ( fakeUser . username ) } )

End to end test:

Simplest definition : Imagine you’ve got a robot that obeys your commands, now ask it to test your app as a normal user 🤷‍♂️.

describe ( 'authentication and registration' , ( ) => { let user beforeEach ( ( ) => { return cy . logout ( ) . createNewUser ( ) . then ( u => ( user = u ) ) . visit ( '/' ) } ) it ( 'register as a guest user' , ( ) => { const user = { username : 'user' , email : 'hello@example.com' , password : 'password123' , } cy . getByText ( /register/i ) . click ( ) . getByLabelText ( /username/i ) . type ( user . username ) . getByLabelText ( /email/i ) . type ( user . email ) . getByLabelText ( /password/i ) . type ( user . password ) . getByText ( /register/i ) . click ( ) . assertRoute ( '/' ) cy . getByTestId ( 'username' ) . should ( 'contain' , user . username ) } ) it ( 'login as a user' , ( ) => { cy . getByText ( /login/i ) . click ( ) . getByLabelText ( /username/i ) . type ( user . username ) . getByLabelText ( /password/i ) . type ( user . password ) . getByText ( /sign in/i ) . click ( ) . assertRoute ( '/' ) cy . getByTestId ( 'username' ) . should ( 'contain' , user . username ) } ) } )

That’s it for this post. Checkout my newsletter 👇 if you want to get notify when I drop new posts. Peace.