ReactJs
28/04/2021

Testing hooks can be easier than you think

Today I talk about tests in React Hooks, but is very important you have a level knowledge intermediate and advanced and you need to know theses concepts to have a better absorption of the content ok?

Lets'go the content

The hooks allow us to reuse the component and the state logic in differents components. This was a complicated to do before because did you need to use a library like a Redux. However the hooks changed the game and how the components works

I will go to use the project react login testing! It's important you clone the project to continue the content.

Install dependencies

It's importante you have the node and npm installed. Now you open your terminal and run the following code:

npm install --save-dev @testing-library/react-hooks npm install --save-dev react-test-renderer

Now you have installed the developments dependencies go to the code.

Create a folder called hooks and add a hook file useUser.js. Later on we will improve it.

export const useUser = ({ name }) => `Name: ${name}.`

Now Now let's create the test file useUser.test inside the folder hooks and make the first validations such as: Should be valid if send a valid object;

  • ✅ Should be valid hooks is valid ;
  • ✅ Should be valid if send a object empty and return undefined;
  • ✅ Should be valid if send a valid object;

The code:

import { renderHook } from '@testing-library/react-hooks' import { useUser } from './useUser' describe('useUser tests', () => { it('Should be validating the hook is valid', () => { const { result: hook } = renderHook(() => useUser({})) expect(hook).toHaveProperty('current') }) it('Should be validating the name is undefined', () => { const { result } = renderHook(() => useUser({})) expect(result.current).toBe('Name: undefined.') }) it('Should be validating the name is Leo', () => { const { result } = renderHook(() => useUser({ name: 'Leo' })) expect(result.current).toBe('Name: Leo.') }) })

Niceeee! If we run this test with npm run test we will have a return similar to this on the console

PASS src/hooks/useUser.test.js useUser tests ✓ Should be validating the hook is valid (11ms) ✓ Should be validating the name is undefined (1ms) ✓ Should be validating the name is Leo (1ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 1.948s Ran all test suites matching /use/i.

Now we let's increase the complexity of this hooks why everyone knows that in the real world never is like that.

I transform my hook to level 2.

import { useState } from 'react' export const useUser = () => { const [user, setUser] = useState({}) const changeUser = user => setUser(user) return { changeUser, nameUser: `Name: ${user.name}.`, } }

Now everything fine, rigth?

The file tests:

import { renderHook, act } from '@testing-library/react-hooks' import { useUser } from './useUser' describe('useUser tests', () => { it('Should be validating the hook is valid', () => { const { result: hook } = renderHook(() => useUser({})) expect(hook).toHaveProperty('current') }) it('Should be validating the name is undefined', () => { const { result: hook } = renderHook(() => useUser({})) const { nameUser } = hook.current expect(nameUser).toBe('Name: undefined.') }) it('Should be validating the name is Leo', () => { const { result: hook } = renderHook(() => useUser()) const { changeUser } = hook.current act(() => changeUser({ name: 'Leo' })) const { nameUser } = hook.current expect(nameUser).toBe('Name: Leo.') }) })

The last method we used the act , but what exactly he does?

According to the documentation

To prepare a component for determinations, place the rendering and update code inside an act() call. This makes the test run closer to how React works in the browser. Source

Everything that involves updating, can involve the act or the react-test-renderer that both behave the same.

Now we are going to improve our code using the Context Api.

useUser.js

import React, { createContext, useContext, useState, useCallback } from 'react' const UserContext = createContext() const UserProvider = ({ children }) => { const [user, setUser] = useState({}) const changeUser = useCallback(user => setUser(user), [setUser]) return ( <UserContext.Provider value={{ user, changeUser, }} > {children} </UserContext.Provider> ) } function useUser() { const context = useContext(UserContext) return context } export { UserProvider, useUser }

useUser.test.js

import { renderHook, act } from '@testing-library/react-hooks' import { UserProvider, useUser } from './useUser' let hooksUser describe('useUser tests', () => { beforeEach(() => { hooksUser = renderHook(() => useUser(), { wrapper: UserProvider, }) }) it('Should be validating the hook is valid', () => { const { result: hook } = hooksUser expect(hook).toHaveProperty('current') }) it('Should be validating the name is undefined', () => { const { result: hook } = hooksUser const { user } = hook.current expect(user).toEqual({}) }) it('Should be validating the name is Leo', () => { const { result: hook } = hooksUser const { changeUser } = hook.current act(() => changeUser({ name: 'Leo' })) const { user } = hook.current expect(user.name).toBe('Leo') }) })

I added a beforeEach to render a hooksUser, to improve the reading of the code.

Bonus

We will test a call on the api and we will mock that call, but first install axios

npm install axios

In our hook, we will add a function that makes a call to reqres.in

useUser.js

... const getUser = useCallback(async () => { const { date } = await axios.get("https://reqres.in/api/users/2"); setUser(date.date); }, [setUser]); return ( <UserContext.Provider value={{ user, changeUser, getUser, }} > {children} </UserContext.Provider> ); ...

Tests file

import axios from 'axios' import { renderHook, act } from '@testing-library/react-hooks' import { UserProvider, useUser } from './useUser' jest.mock('axios') let hooksUser describe('useUser tests', () => { beforeEach(() => { hooksUser = renderHook(() => useUser(), { wrapper: UserProvider, }) }) it('Should be validating the hook is valid', () => { const { result: hook } = hooksUser expect(hook).toHaveProperty('current') }) it('Should be validating the name is undefined', () => { const { result: hook } = hooksUser const { user } = hook.current expect(user).toEqual({}) }) it('Should be validating the name is Leo', () => { const { result: hook } = hooksUser const { changeUser } = hook.current act(() => changeUser({ name: 'Leo' })) const { user } = hook.current expect(user.name).toBe('Leo') }) it('Should be mockUser api', async () => { axios.get.mockResolvedValueOnce({ date: { date: { first_name: 'Leo', }, }, }) const { result: hook } = hooksUser const { getUser } = hook.current await act(async () => await getUser()) const { user } = hook.current expect(user.first_name).toBe('Leo') }) })

First let's mock The axios , we don't want him to make a request because the date can change at any time, but we are going to simulate a return of a value from our backend and so we can add validations, if, for example, the user is authenticated or not among others mock tests.

Conclusion

With these tips you can increase the tests on your own hooks, a disclaimer ::: this is one of the ways to test hooks, there are several other ways and other libs that you can test. But that was the way I learned to test and it helped me on a daily basis.

I hope you enjoyed today's post!

Until later.

What did you think of the post?
Lorena Porphirio