> For the complete documentation index, see [llms.txt](https://tutorial.specian.co.uk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tutorial.specian.co.uk/frontend/end-to-end-tests.md).

# Writing tests

This is what we want to test in `SignupPage.tsx`:

* The form is filled up correctly.
* The values submitted come from the form.
* The token is added to the local storage.
* The user is redirected to the home page.

To achieve this, we will need to:

* Simulate keyboard and mouse events.
* Mock Apollo provider.
* Mock `localStorage`.
* Mock `history`.

### Simulate keyboard and mouse events

There are two ways to do this: `Simulate` from `react-dom/test-utils` or using `dispatchEvent`:

```typescript
import { act, Simulate } from "react-dom/test-utils";

const emailInput = container.querySelector("input[name=emailAddress]")!;
emailInput.setAttribute("value", "test@example.com");
Simulate.change(emailInput);

const submitButton = container.querySelector("button[type=submit]")!;
expect(submitButton.textContent).toEqual("Submit");
submitButton.dispatchEvent(new MouseEvent("click"));
```

{% hint style="info" %}
Learn more about rendering components with `test-utils` from the [Testing Recipes](https://reactjs.org/docs/testing-recipes.html).
{% endhint %}

### Mock Apollo provider

We need to export the mutation from the page to be able to use it in our test:

{% code title="pages/SignupPage.tsx" %}

```typescript
export const SIGNUP_MUTATION = gql`
  mutation signUp($input: UserAuthInput!) {
    signUp(input: $input) {
      id
      emailAddress
      jwt
    }
  }
`;

export const SignupPage = () => { ... }
```

{% endcode %}

We can now use `MockedProvider` from[`@apollo/react-testing`](https://www.apollographql.com/docs/react/api/react-testing/).

```typescript
const mocks = [
    {
      request: {
        query: SIGNUP_MUTATION,
        variables: {
          input: {
            emailAddress: "test@example.com",
            password: "qwerty123"
          }
        }
      },
      result: {
        data: {
          signUp: {
            id: "userid",
            emailAddress: "test@example.com",
            jwt: "faketoken"
          }
        }
      }
    }
  ];
  
await act(async () => {
   render(
     <MockedProvider mocks={mocks} addTypename={false}>
       <SignupPage />
     </MockedProvider />
    );
});
```

{% hint style="info" %}
Learn more about `MockedProvider`with [Testing React components](https://www.apollographql.com/docs/react/development-testing/testing/).
{% endhint %}

### Mock local storage

Jest comes with ways to easily mock functions:

```typescript
const spy = jest.spyOn(Storage.prototype, "setItem");
// ... expect(window.localStorage.setItem).to..;
spy.mockRestore();
```

The important part is to know how to mock things. (e.g `Storage.prototype` for `localStorage`).

### Mock history

We can use `createMemoryHistory` from[`history`](https://github.com/ReactTraining/history).

```typescript
const history = createMemoryHistory({ initialEntries: ["/signup"] });
```

```typescript
<Router history={history}>
  <Route path="/signup" component={SignupPage} />
</Router>
```

### Bring it together

{% code title="pages/SignupPage.spec.tsx" %}

```typescript
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act, Simulate } from "react-dom/test-utils";
import { SignupPage, SIGNUP_MUTATION } from "./SignupPage";
import { createMemoryHistory } from "history";
import { Router, Route } from "react-router";
import { MockedProvider, wait } from "@apollo/react-testing";
import { ThemeProvider } from "styled-components";

let container: HTMLDivElement;
beforeEach(() => {
  // setup a DOM element as a render target
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  // cleanup on exiting
  unmountComponentAtNode(container);
  container.remove();
  container = (null as unknown) as HTMLDivElement; // prevent memory leaks
});

it("renders user data", async () => {
  // setup the test
  const history = createMemoryHistory({ initialEntries: ["/signup"] });
  let signUpMutationCalled = false;
  const spy = jest.spyOn(Storage.prototype, "setItem");

  const mocks = [
    {
      request: {
        query: SIGNUP_MUTATION,
        variables: {
          input: {
            emailAddress: "test@example.com",
            password: "qwerty123"
          }
        }
      },
      result: () => {
        signUpMutationCalled = true;
        return {
          data: {
            signUp: {
              id: "userid",
              emailAddress: "test@example.com",
              jwt: "faketoken"
            }
          }
        };
      }
    }
  ];

  await act(async () => {
    render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <ThemeProvider theme={{}}>
          <Router history={history}>
            <Route path="/signup" component={SignupPage} />
          </Router>
        </ThemeProvider>
      </MockedProvider>,
      container
    );

    // fill up the form
    const emailInput = container.querySelector("input[name=emailAddress]")!;
    emailInput.setAttribute("value", "test@example.com");
    Simulate.change(emailInput);

    const passwordInput = container.querySelector("input[name=password]")!;
    passwordInput.setAttribute("value", "qwerty123");
    Simulate.change(passwordInput);
  });

  await act(async () => {
    // submit the form
    // triggering the hook has to be in another `act`
    const submitButton = container.querySelector("button[type=submit]")!;
    expect(submitButton.textContent).toEqual("Submit");
    submitButton.dispatchEvent(new MouseEvent("click"));
    await wait(0);
    // check loading state
    expect(submitButton.textContent).not.toEqual("Submit");
  });

  // check if the mutation has been  called
  expect(signUpMutationCalled).toBeTruthy();

  // check if the token has been set correctly
  expect(window.localStorage.setItem).toHaveBeenCalledWith(
    "token",
    "faketoken"
  );
  spy.mockRestore();

  // check if the user has been redirected
  expect(history.location.pathname).toBe("/");
});
```

{% endcode %}

{% hint style="info" %}
[`react-unit-test`](https://github.com/florianherrengt/book-code/tree/react-unit-test) branch and [pull request](https://github.com/florianherrengt/book-code/tree/react-unit-test) available on GitHub.
{% endhint %}
