# Testing Guide At a high level there are several categories of tests, which have often been represented as the testing triangle. In the image below, you can see at the tip are e2e tests (end-to-end), in the middle are integration tests and at the base are unit tests. ![](https://i.imgur.com/vu5Vham.png) We should strive to always have unit tests for our components and be more selective about adding in e2e testing. At the moment this guide only dives into creating unit tests, but will eventually provide guides on how to create e2e tests for Detox (Companion) and Cypress (Bridge). Here is a breakdown of how that translates to our tests at Pathfinder: ## Companion Here's how the testing triangle translates in Companion. Note how we use Jest for both unit and integration testing. Detox actually uses Jest under the hood, so technically we use Jest for all the things! ![](https://i.imgur.com/7bx0Ti7.png) ## Bridge Here's how the testing triangle translates in Bridge. The only difference is that we use Cypress for e2e testing. ![](https://i.imgur.com/YPLlYJ6.png) ## Writing Unit Tests In the example below, we are writing a unit test for a web-based component. The same approach will be used for react-native, except that rather than `@testing-library/react`, we use `@testing-library/react-native`. To generate the snapshot test is also slightly different (we have a helper for this in `utils/testHelpers`). When we write unit tests, there is more mocking because we want to isolate the "unit" we are testing. Let's write a unit test for the following component: ``` import React, { useEffect, useState } from 'react'; import axios from 'axios'; import {List} from '../List'; import {ListItem} from '../ListItem'; export const MyList = ({ userId, isDisabled }) => { const [myData, setMyData] = useState(null); useEffect(() => { (async () => { const data = await axios.get('https://myapi.com?userId=${userId}'); setMyData(data); })() }, [userId]); if (!myData) return null; return ( <List> { myData.map(myDataItem => <ListItem itemData={myDataItem} />) } </List> ) } ``` ### How to approach this Unit Test **1) Determine what to mock:** Since we are writing a unit test for this component, we need to determine what we need to mock to isolate this test. Here's what we'll need to mock: - `axios` - We want to control what data is returned from our api endpoint, so we'll need to mock axios. - `List` and `ListItem` - These components should already be tested through their own respective unit tests, so we will want to mock these as well. We don't necessarily care how these work, we only know we are using them. What about the `useEffect` or `useState`? We do not want to mock these. These function as part of this component and we should leave these be. **2) Determine what to test:** We can see 2 pathways through this component. 1. When `myData` is null, we return `null` 2. When `myData` is not null, we return the rendered component These are the basic pathways we want to ensure to test. Now that we know what to mock and what to test, let's write the unit test. ### Writing the Test Tests reside in a `__tests__` folder at the same level as the component. 1) Create `MyList-test.js` in `__tests__` 2) Let's start with a basic test setup that imports our component and renders it to a snapshot. _this is only an example...not ready yet_ ``` import React from 'react'; // This is used to render the component import {act, cleanup, render, screen} from '@testing-library/render'; // Import component to test import {MyList} from '../MyList'; describe('<MyList />', () => { const defaultProps = { userId: 'USER_ID', isDisabled: false }; // Let's create a helper to render our component const renderComponent = overrides => { return render(<MyList {...defaultProps} {...overrides} />); } afterEach(async () => { // Clear out the DOM await cleanup(); }); it('renders as expected', () => { // Act waits for useEffect act(() => { renderComponent(); }); // This will create a new file in a snapshots folder expect(screen.container.firstChild).toMatchSnapshot(); }) }) ``` 3) There are some things missing here, specifically mocking. If we do not mock, we will be making real API calls as well as rendering the <List /> and <ListItem /> components, which can make our test brittle. Remember, unit tests are the most isolated, so we want to protect the boundaries of the unit under test by mocking things that we don't really care about. Here is our example, fully-flushed with mocks and everything: ``` import React from 'react'; import {act, cleanup, render, screen} from '@testing-library/render'; // We are mocking axios below, so "axios" in the line below will actually be a mock, not the real axios import axios from 'axios'; // Import component to test import {MyList} from '../MyList'; // Mock axios to control api call jest.mock('axios') // Mock other components jest.mock('../../List', () => ({ List: props => <x-list data-testId='list' {...props} /> })); jest.mock('../../ListItem', () => ({ ListItem: props => <x-list-item {...props} /> })); describe('<MyList />', () => { const defaultProps = { userId: 'USER_ID', isDisabled: false }; // Let's create a helper to render our component const renderComponent = overrides => { return render(<MyList {...defaultProps} {...overrides} />); } afterEach(async () => { // Clear out the DOM await cleanup(); }); describe('when the API returns no data', () => { beforeEach(() => { axios.mockImplementation(() => ({ get: () => Promise.resolve(null) })); }) it('does not render the list', () => { act(() => { renderComponent(); }); // No need for a snapshot here, we can just check for the non-existance of the list // See how we were able to add the test id in the mock above expect(screen.queryByTestId('list')).toBeNull(); }); }); describe('when the API returns data', () => { const mockData = [{ id: 1, name: 'NAME_1' }, { id: 2, name: 'NAME_2' }] beforeEach(() => { // Let's mock axios to make sure we have data for this test axios.mockImplementation(() => ({ get: () => Promise.resolve(mockData) })); }) it('renders as expected', () => { act(() => { renderComponent(); }); expect(screen.container.firstChild).toMatchSnapshot(); // Let's also make sure we made the right api call expect(axios.get).toHaveBeenCalledWith('https://myapi.com?userId=USER_ID') }); }); }) ```