In this article, we’ll discuss how to test React components where the data and data changes within the component depend on a GraphQL API. Before we dive in, let’s quickly refresh our understanding of GraphQL and the React library we’ll use to interact with a GraphQL API, React Apollo.
Jump ahead:
GraphQL
GraphQL is a flexible and efficient way to interact with APIs, allowing clients to specify exactly what data they need and receive responses with the requested information. GraphQL can be used from both the perspective of a client, such as a frontend web application, or a server.
To retrieve data in GraphQL, queries are used. For example, the following query could be used to request a list of to-do items from a server:
query TodoList { todoList { id title description } }
The query above would return a list of todo
item objects with id
, title
, description
, and date
fields.
In addition to retrieving data, GraphQL also supports mutations to make changes to the server. A simple example of a mutation to delete a specific to-do item from a database might look like this:
mutation deleteTodo($id: ID!) { deleteTodo(id: $id) { id } }
This mutation takes in the id
of the to-do item to be deleted and returns the id
of the deleted item when successful.
React Apollo library
Apollo Client, built by the Apollo GraphQL team, is a toolkit designed to make it easy for a client application to communicate with a GraphQL API. The React Apollo library offers a set of specific tools that can be integrated into React components.
useQuery()
One of the main functions provided by React Apollo to execute GraphQL queries is the useQuery()
Hook. This Hook takes a GraphQL document as its first argument and returns a result
object that includes the data
, loading
, and error
statuses of the query request.
For example, consider the following TodoList
component that uses the useQuery()
Hook to request data from the TodoList
query example we shared above:
import * as React from 'react'; import { useQuery } from "@apollo/react-hooks"; const TODO_LIST = ` query TodoList { todoList { id title description } } `; export const TodoList = () => { const { loading, data, error } = useQuery(TODO_LIST); if (loading) { return <h2>Loading...</h2> } if (error) { return <h2>Uh oh. Something went wrong...</h2> } const todoItems = data.map((todo) => { return <li>{todo.title}</li> }) return ( <ul>{todoItems}</ul> ) }
In this example, the component displays a loading message while the query request is in progress, an error message if the request fails, and a list of to-do titles if and when the request is successful.
Attempting to render the above component in a unit test would likely result in failure due to a lack of context for the query or its results (loading
, data
, error
, etc.). Because of this, we can use the @apollo/react-testing library to mock the GraphQL request. Mocking the request allows us to properly test the component without relying on a live connection to the API.
MockedProvider
The @apollo/react-testing
library includes a utility called MockedProvider
that allows us to create a mock version of the ApolloProvider
component for testing purposes. The ApolloProvider
component is a top-level component that wraps our React app and provides the Apollo client as context throughout the app.
MockedProvider
enables us to specify the exact responses we want from our GraphQL requests in our tests, allowing us to mock these requests without actually making network requests to the API.
Let’s see how we can mock GraphQL requests in the loading, error, and success states.
Mocking GraphQL requests
Loading state
To mock GraphQL requests in the loading state, we can wrap our components with MockedProvider
and provide an empty array as the value of the mocks
prop:
import * as React from 'react'; import { render } from "@testing-library/react"; import { TodoList } from '../TodoList'; describe("<TodoList />", () => { it("renders the expected loading message when the query is loading", async () => { const { /* get query helpers*/ } = render( <MockedProvider mocks={[]}> <TodoList></TodoList> </MockedProvider> ); // assertions to test component under loading state }); });
Assuming we’re using Jest as the unit testing framework and react-testing-library as the testing utility, we can assert that the component renders the expected text in its markup:
import * as React from 'react'; import { render } from "@testing-library/react"; import { TodoList } from '../TodoList'; describe("<TodoList />", () => { it("renders the expected loading message when the query is loading", async () => { const { queryByText } = render( <MockedProvider mocks={[]}> <TodoList /> </MockedProvider> ); // assert the loading message is shown expect(queryByText('Loading...')).toBeVisible(); }); });
Error state
By wrapping our components with MockedProvider
and providing mock request
and error
(or errors
) property values, we can simulate the error state by specifying the exact error responses we want for our GraphQL request:
import * as React from 'react'; import { render } from "@testing-library/react"; import { TodoList } from '../TodoList'; const TODO_LIST = ` query TodoList { todoList { id title description } } `; describe("<TodoList />", () => { // ... it('renders the expected error state', async () => { const todoListMock = { request: { query: TODO_LIST, }, error: new Error('Network Error!'), }; const { /* queries */ } = render( <MockedProvider mocks={[todoListMock]}> <TodoList></TodoList> </MockedProvider>, ); // assertions to test component under error state }); });
We’ll have our unit test assert that the <TodoList />
component correctly displays the expected error message when the TodoList
GraphQL query has failed with a network error:
import * as React from 'react'; import { render } from "@testing-library/react"; import { TodoList } from '../TodoList'; const TODO_LIST = ` query TodoList { todoList { id title description } } `; describe("<TodoList />", () => { // ... it('renders the expected error state', async () => { const todoListMock = { request: { query: TODO_LIST, }, error: new Error('Network Error!'), }; const { queryByText } = render( <MockedProvider mocks={[todoListMock]}> <TodoList></TodoList> </MockedProvider>, ); // assert the error message is shown expect(queryByText('Uh oh. Something went wrong...')).toBeVisible(); }); });
Success state
Lastly, to test GraphQL requests in a successful state, we can use the MockedProvider
utility component to wrap our components and provide mock values for the request
and result
properties in the mock GraphQL object. The result
property is used to simulate the expected successful outcome of the GraphQL request in our test:
import * as React from 'react'; import { render } from "@testing-library/react"; import { TodoList } from '../TodoList'; const TODO_LIST = ` query TodoList { todoList { id title description } } `; describe("<TodoList />", () => { // ... // ... it('renders the expected UI when data is available', async () => { const todoListMock = { request: { query: TODO_LIST, }, result: { data: { todos: [ { id: '1', title: 'Todo Item #1', description: 'Description for Todo Item #1', }, { id: '2', title: 'Todo Item #2', description: 'Description for Todo Item #2', } ] }, }, }; const { /* queries */ } = render( <MockedProvider mocks={[todoListMock]}> <TodoList></TodoList> </MockedProvider>, ); // assertions // ... }); });
GraphQL API requests are asynchronous, meaning that we often need to specify a waiting period in our tests before making assertions about the request’s outcome. React Testing Library provides the waitFor
utility to handle this scenario.
waitFor
can be used in a unit test when mocking an API call and waiting for the mock promises to resolve. An example of using waitFor
in the above unit test would be:
import * as React from 'react'; import { render, waitFor } from "@testing-library/react"; import { TodoList } from '../TodoList'; const TODO_LIST = ` query TodoList { todoList { id title description } } `; describe("<TodoList />", () => { // ... // ... it('renders the expected UI when data is available', async () => { const todoListMock = { request: { query: TODO_LIST, }, result: { data: { todos: [ { id: '1', title: 'Todo Item #1', description: 'Description for Todo Item #1', }, { id: '2', title: 'Todo Item #2', description: 'Description for Todo Item #2', } ] }, }, }; const { queryByText } = render( <MockedProvider mocks={[todoListMock]}> <TodoList></TodoList> </MockedProvider>, ); // use the waitFor utility to wait for API request to resolve await waitFor(() => { // assert the title for each todo item is visible expect(queryByText('Todo Item #1')).toBeVisible(); expect(queryByText('Todo Item #2')).toBeVisible(); }); }); });
Conclusion
Unit testing is a crucial aspect of software development. It verifies that our code is doing what we expect it to do and ensures that it works correctly with other code it interacts with.
When testing React components that communicate with an API, it’s important to avoid making actual API requests in our unit tests to save time and resources. Mocking GraphQL API requests, as described in this article, allows us to efficiently test our React components’ behavior without the added overhead of actual API requests.
The post Mocking GraphQL requests using the React Apollo library appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/51keFf9
Gain $200 in a week
via Read more