Test your React App with Context
Tags: react, redux, typescript, testing
Published: 2020-04-15 12:47:55

Have you ever struggled to test this little `fetch()` call or this `window.location` in your React App? The thing with those Web APIs is that you cannot mock them directly. Of course you can globally mock the fetch API during test setup [like this](https://www.reactnativeschool.com/mocking-fetch-api-calls-when-using-jest) or use [an npm package](https://www.npmjs.com/package/jest-fetch-mock) to do the same thing. But what to do with the next fancy API? I say you can solve the problem much easier and end up with a cleaner architecture at the same time by wrapping the APIs into a React.Context.

First let us define a very thin wrapping layer of all APIs we need to use
```typescript
export interface Api {
    fetch: typeof fetch
}

export const createApi: () => Api = () => ({ fetch })
export const mockedApi: () => Api = () => ({ fetch: jest.fn() })
```

You can create the Api in two ways. One in your production code with `createApi` and one in your tests with `mockedApi`. The problem is that you can't just invoke `fetch()` wherever you like anymore. You first have to retrieve the Api object from somewhere. If you call the `createApi()` method whenever you need the object, you still cannot replace the Api by a mock during testing. You need to somehow pass the object through your whole App and put it into the Props of all your Components. This is not very elegant and a lot of work!

Luckily, React comes with a solution. You can create a [React.Context](https://reactjs.org/docs/context.html) object, put your Api into it and consume this context wherever you need it.

I do not like to use my API directly from my Components, so I first create Service objects.

```typescript
export interface Services {
    users: UsersService
    contacts: ContactsService
    rest: RestService
}
const createServices = (): Services => {
    const api = createApi()
    const contacts = new ContactsService(api)
    const rest = new RestService(api)
    const entities = new EntityService(api)
    return { contacts, rest, entities }
}
```

When testing these services you can easily wrap the Api and focus on the interaction with the Api. For a component to use these services you have to put them into a React.Context.

```typescript
export const Services = React.createContext<Services | undefined>(undefined)

const AppWithContext = (): JSX.Element => (
    <Services.Provider value={createServices()}>
        <App />
    </Services.Provider>
)
```

This is exactly how you provide a [Redux Store](https://redux.js.org/basics/usage-with-react#indexjs) to your App. Let us write something very similar to redux's [connect](https://react-redux.js.org/api/connect) function.

```typescript
export const injectServices = <P extends object>(
    WrappedComponent: React.ComponentType<P & Services>
): React.ComponentType<P> => props => (
    <Services.Consumer>
        {services => services && <WrappedComponent {...services} {...props} />}
    </Services.Consumer>
)
```

This function takes a Component that has some Props `P & Services` and returns a Component that has only Props `P`. You can easily use it like this

```typescript
type Props = Services & OwnProps
export injectServices(
    (props: Props): JSX.Element => <></>
)
```

and you can even put a connected Component into this function.

```typescript
export const dispatchToProps: (
    dispatch: Dispatch,
    props: Service & OwnProps
) => Callbacks = (dipatch, props) => ({
    onSave: () => dipatch(createActionUsingService(props))
})

export default injectServices(
    connect(
        stateToProps,
        dispatchToProps
    )(MyComponent)
)

```
Now you can use the services even in your mapping function, which is probably exactly where you want them.

_If you like this post, why not have a look at one of my other (probably more light-hearted) posts. This post was sponsored by [itemis AG](https://www.itemis.com/en/). That's the place were I work and drink._