Ankush Negi

React Hooks Unit Testing using Shallow

By Ankush Negi

Last updated cal_iconFebruary 24, 2022

React Hooks let you use state and other React features without writing a class. It solves a wide variety of seemingly unconnected problems in React that we have encountered over five years of writing and maintaining tens of thousands of components. Whether you’re learning React, using it every day, or prefer a different library with a similar component model, you might recognize some of these problems.

Why shallow?

  • The shallow method allows us to render a single component that we are testing.
  • It doesn’t render the child component.
  • Less props are required as there is only a single component rendering.
  • In Enzyme version 3, the shallow method can access the lifecycle methods.

Note: I had analyzed the problem of undefined data in response from MockedProvider when using a shallow method. So, I tried another method to mock the hooks to test the components. In this article, the testing is done by shallow method, and MockedProvider or client isn’t used.

Let’s Start…

I had used Jest and Enzyme for testing the components. So, refer to the documentation for the installation of dependencies and packages used. And for configuring the Jest, click here.

File: Example.js

const Example = (props) => {
const { tool, handleEndorsement, …rest } = { …props };
const { userId: uId } = useContext(AuthContext);
const [pageNumber] = useState(0)
const { data, loading, refetch } = useQuery(GET_DATA, {
fetchPolicy: ‘no-cache’,
variables: {
skip: 0,
limit: 25,
input: {
uId,
toolId: tool.originalId,
},
},
});

const refetchFunc = async (val) => {
await handleEndorsement(val, refetch)
await refetch()
}

return (
<Example1
handleEndorsement={refetchFunc}
refetch={() => refetch()}
data={data}
pageNumber={pageNumber}
tool={tool}
isloading={loading}
{…rest}
/>
)
}

In this example, useQuery, useContext, and useState hooks are used. For testing, we use the jest method spyOn to mock the hooks. SpyOn provides a straightforward way to mock the functions.

import React, * as ReactModule from ‘react’;
import { shallow } from ‘enzyme’;
import * as reactApollo from ‘@apollo/client’;
import Example from ‘./Example’;

const props = {
tool: {
tool1: ‘tool1’,
tool2: ‘tool2’
},
handleEndorsement: jest.fn(),
text: ‘test’,
};

const data = {
refetch: jest.fn(),
data: {
getEndorsements: {
data: {
count: 1,
result: [
{
title: ‘title’,
likes: ‘likes’,
includes: ‘includes’,
},],
},
},
},
};
const contextValues = {
userId: ‘userID’,};

First, we have to import all the files and packages, after that, the mock must be declared and defined. 

jest.spyOn(ReactModule, ‘useContext’).mockImplementation(() => contextValues);
jest.spyOn(reactApollo, ‘useQuery’).mockImplementation(() => data);

The mock implementation is then added to the test using the spyOn method. So, whenever a useQuery or useContext is called in the Example.js file the spyOn provides a mock for that call. Now let’s write some test cases using Enzyme and jest.

describe(‘success test case for Example’, () => {
beforeAll(() => {
wrapperMount = shallow(<Example {…props} />);
});
it(‘to match snapshot’, () => {
expect(wrapperMount).toMatchSnapshot();
});
it(‘should render refetch’, () => {
const refetch = wrapperMount.prop(‘refetch’);
refetch();
});
it(‘should render refetchFunc’, async () => {
const refetchFunc = wrapperMount.prop(‘handleEndorsement’);
await refetchFunc(‘val’);
});
});

You can add expect the method to the return value after calling the function. 

Let’s move forward for mutation and subscription…

Mutation: In GraphQl, the mutation is used for CUD:

  • Create new data in the database
  • Update the existing data in the database
  • Delete the existing data in the database

The syntax for Query and Mutation is almost the same, but mutation must start with the mutation keyword.

Subscription: In GraphQl, a subscription is a way to create and maintain a real-time connection with the server. It is based on events. It enables the client to get an immediate response from the server about the event. Basically, a client had to subscribe for an event on the server, and whenever the event is called, the server sends the response related to that event.

Let’s start with the useMutation hook.

File: extra.js

import React from ‘react’;
import { useMutation } from ‘@apollo/client’;
import gql from ‘graphql-tag’;

export const DELETE_DOG_MUTATION = gql`
mutation deleteDog($name: String!) {
deleteDog(name: $name) {
id
name
breed
}}`;

export function DeleteButton() {
const [mutate, { loading, error }] = useMutation(DELETE_DOG_MUTATION);

if (loading) return <p>Loading…</p>;
if (error) return <p>Error!</p>;

return (
<button onClick={() => mutate({ variables: { name: ‘Buck’ } })}>
Click me to Delete Buck!
</button>
);}

The above example is taken from here. In this example, the useMutation hook is used for mutating the data.

Now let’s test using shallow.

import React from ‘react’;
import { shallow } from ‘enzyme’;
import * as reactApollo from ‘@apollo/client’;
import { DeleteButton } from ‘./extra’

const result = {
data: {
id: ‘test’,
name: ‘test’,
breed: ‘breed’,
},}

const mutate = jest.fn()
.mockImplementation(() => new Promise(resolve => resolve(result)))

const value = [
mutate,
{
loading: false,
error: undefined,
},]

jest.spyOn(reactApollo, ‘useMutation’).mockImplementation(() => value);

let wrapperMount;

describe(‘success test case for PlannerTool’, () => {
beforeAll(() => {
wrapperMount = shallow(<DeleteButton />)
})
it(‘to match snapshot’, () => {
expect(wrapperMount).toMatchSnapshot();
});
it(‘should handle onClick’, () => {
const click = wrapperMount.prop(‘onClick’);
click();
});});

The onClick prop here can also be tested using a simulated method. And to test the loading and error, you have to change the value of loading and error inside the value variable. This is a way to mock the useMutation hook so as to test your code.

Now, useSubscription.

File: extra.js

import React from ‘react’;
import { useSubscription } from ‘@apollo/client’;
import gql from ‘graphql-tag’;

const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($repoFullName: String!) {
commentAdded(repoFullName: $repoFullName) {
id
content
}}`;

export default function DontReadTheComments({ repoFullName }){
const {
data: { commentAdded },
loading,
} = useSubscription(COMMENTS_SUBSCRIPTION, { variables: { repoFullName } });
return <h4>New comment: {!loading && commentAdded.content}</h4>;}

The above example is taken from here. In this example, the useSubscription hook is used for comments added in the repo.

Let’s test… 

import React from ‘react’;
import { shallow } from ‘enzyme’;
import * as reactApollo from ‘@apollo/client’;
import DontReadTheComments from ‘./extra’

const value = {
data: {
commentAdded: {
id: ‘id’,
content: ‘test’
},
loading: false,
},
}

jest.spyOn(reactApollo, ‘useSubscription’).mockImplementation(() => value);

const props = {
repoFullName: ‘test repo’,
}

let wrapperMount;
describe(‘success test case for PlannerTool’, () => {
beforeAll(() => {
wrapperMount = shallow(<DontReadTheComments {…props} />)
})
it(‘to match snapshot’, () => {
expect(wrapperMount).toMatchSnapshot();
});
});

This is a simple example of a useSubscription hook. You can mock more than one subscription using a single spyOn function. It is more helpful when there is more number of subscription hooks used.

subscribeToMore: There is one more function in graphQl i.e. subscribeToMore. The subscribeToMore function is similar in structure to the fetchMore function that’s commonly used for handling pagination. The primary difference is that fetchMore executes a follow-up query, whereas subscribeToMore executes a subscription(ref).

So, I had tried it too to test using shallow.

const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}}`;

export default function CommentsPageWithData({ params }) {
console.log(‘value of propss’, params);
const { subscribeToMore, …result } = useQuery(
COMMENT_QUERY,
{ variables: { postID: params.postID } }
);

return (
<CommentsPage
{…result}

subscribeToNewComments={() =>
subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: { postID: params.postID },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
post: {
comments: [newFeedItem, …prev.post.comments]
}
});
}
})
}
/>
);
}

To fulfill all the requirements, I added up something extra in that.

const COMMENT_QUERY = gql`
query CommentsForPost($postID: ID!) {
post(postID: $postID) {
comments {
id
content
}
}
}
`;

The above example is taken from the official website of apollo graphql. If needed, take a reference from here.

Now, let’s test…

import React from ‘react’;
import { shallow } from ‘enzyme’;
import * as reactApollo from ‘@apollo/client’;
import CommentsPageWithData from ‘./extra’

const prev = {
post: {
comments: ”,
},
};

const value = {
loading: false,
subscribeToMore: jest.fn().mockImplementation((obj) => {
obj.updateQuery(prev,
{
subscriptionData: {
data: {
commentAdded: ‘commentAdded’
},
},
});
}),
error: undefined,
refetch: jest.fn(),
result: ‘test success’,
}

jest.spyOn(reactApollo, ‘useQuery’).mockImplementation(() => value);

const props = {
params: {
postID: ‘testID’,
},};

let wrapperMount;

describe(‘success test case for PlannerTool’, () => {
beforeAll(() => {
wrapperMount = shallow(<CommentsPageWithData {…props} />)
})
it(‘to match snapshot’, () => {
expect(wrapperMount).toMatchSnapshot();
});
it(‘should render subscribeToMore’, () => {
const subToMore = wrapperMount.prop(‘subscribeToNewComments’);
subToMore();
})
});

In the testing, I used spyOn to mock the useQuery and inside it, I had mocked the SubscribeToMore. Thus, this provides the response as the event occurred.

Note: In testing, I had not used expect as it changes with person code, but if required can be added up.

Conclusion: There is no need to use MockedProvider or client in the testing when you are using shallow because the method renders only a single component and doesn’t depend on any other component.

Have a happy Testing

Reference: https://reactjs.org

 

Get In Touch

How Can We Help ?

We make your product happen. Our dynamic, robust and scalable solutions help you drive value at the greatest speed in the market

We specialize in full-stack software & web app development with a key focus on JavaScript, Kubernetes and Microservices
Your path to drive 360° value starts from here
Enhance your market & geographic reach by partnering with NodeXperts