Quick template to test Redux Tool Kit and React Router with Jest
Tags: javascript, typescript, react, redux
Published: 2020-08-22 23:55:13

Redux Tool Kit and React Router is very useful in React development process. Unfortunately, there aren't too much information/post about boilerplate that directly fitting redux tool kit. But I'm sharing my own quick test utils function and setup in testing with typescript.

Assuming we are using the React-Router and Redux Tool Kit in our application. And we select the Jest and React Testing Library(RTL) as our test framework.

# Start wrapping!

First function we need is a function that wrapper any component into it, and then we can use `render` method in RTL to directly render component and also cover the reducer/actioncreator in redux.

```typescript
import { configureStore, Store } from '@reduxjs/toolkit'
import { ConnectedRouter } from 'connected-react-router'
import React, { ComponentType, ReactElement } from 'react'
import { Provider } from 'react-redux'
import { history, middleware, reducer } from '../app/store'

export const makeStore = (): Store => {
  return configureStore({ reducer, middleware })
}

const wrapComponent = (Component: ComponentType, store: Store | null = null, props = {}): ReactElement => {
  return (
    <Provider store={store || makeStore()}>
      <ConnectedRouter history={history}>
        <Component {...props} />
      </ConnectedRouter>
    </Provider>
  )
}

export default wrapComponent
```

What this function do is to provide redux and react/router context for our component. You can provide your own `store`, if you want to get the information in test. And props can also be manually passed in to render display component.

If we want to test our `<AwesomeComponent />`. We can just write such code in jest.

In our AwesomeComponent.spec.ts: 
```typescript
import wrapComponent from 'wrapComponent'
import { render, screen } from '@testing-library/react'
import AwesomeComponent from 'AwesomeComponent'


describe('<HostCheckoutContainer />', () => {
    beforeEach(() => {
      render(wrapComponent(AwesomeComponent))
    })

    it('should do some testing', () => {
        expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument()
    })
}

```

And then... We are done! We can write test on any component we want, and they will be just running fine. But don't forget to write `mockApi.ts`! Our component shouldn't rely on Database, network... But our reducer will be automatically included in our test. Moreover, If you are writing integration test, just put the `<App />`(the very top component) in `warpComponent`, you can simulate page jump as well! Woo-woo, that's really cool~

#### It is not recommended, but you may want...

If you really want to check the redux store state in your test. You are also able to do this using the boilerplate above. All our redux state is stored in `store`. We can use `store.getState()` to check it anytime you want.

Imagine we simulate user to click a button/link and navigate to another page. We can simple check our redux store to determine the url is changed or not.

```typescript
describe('<AwesomeComponent />', () => {
    it('should test redux state', () => {
        const store = makeStore()
        render(wrapComponent(HostDetailContainer, store))
        button = screen.getByRole('button', { name: /detail/i })
        expect(store.getState().router.location.pathname).toEqual('/article/1/')
    })
}
```

# Quick url Hacks using react-router.

React router is fantastic routing tool in Single Page Application development, especially we are using some history hooks make life easier. However, we may encounter several common pattern problem using it. No worries, we can hack it!

When we doing web development, it is quite normal that we use Restful API. And in our component with routing role, we may write codes like this...
``` typescript
import { Route, Switch } from 'react-router'

const Routing: (): ReactElement => {
  return (
    <Switch>
      {/* Host Router */}
      <Route path="/article/:id" render={() => <AwesomeRestfulComponent />} />
    </Switch>
  )
}
```

In order to get the `:id` value, you are inclined to use hooks in your component.
``` typescript
const { id } = useParams()
```

But we don't access our component directly using url, we can't get `id` variable from that. Luckily, we got a quick solution. We can add such line in our `AwesomeRestfulComponent.spec.ts`...

``` typescript
import routeData from 'react-router'

describe('<AwesomeRestfulComponent />', () => {
  beforeEach(() => {
    jest.spyOn(routeData, 'useParams').mockReturnValue({ id: '1' })
    render(wrapComponent(AwesomeRestfulComponent))
  })

   // some test...
}

```

We are now able to use the manually added id in our component, sweet!

# Test component without redux

We also write some small reusable component, and for thoses component redux store normally does not provide state for the component. Thus we hope we can use `render(<NavBar />)` to test it, but not in wrapped component. We can hack the `useHistory` hook in `react-router`

We have our `<NavBar />` component as following...
```typescript
export type Props = {
  children?: React.ReactNode
  path?: string
}

const NavBar = (props: Props): ReactElement => {
  const history = useHistory()

  const { children, path } = props

  const onClick = useCallback(() => {
    if (path) {
      history.push(path)
    } else {
      history.goBack()
    }
  }, [history, path])

  return (
    <>
      <Row>
        <Button type="icon" onClick={onClick}>
          Go to somewhere...
        </Button>
        <Heading level={3}>{children}</Heading>
      </Row>
    </>
  )
}

export default NavBar

```

We can hack `useHistory` in such way in our test...

```typescript
const mockHistoryPush = jest.fn()
const mockHistoryGoBack = jest.fn()

jest.mock('react-router', () => ({
  useHistory: () => ({
    push: mockHistoryPush,
    goBack: mockHistoryGoBack,
  }),
}))

describe('<PageNavBar />', () => {
  const props: Props = {
    children: 'Detail',
    path: '/123',
  }

  it('should render properly', () => {
    render(<NavBar {...props} />)
    userEvent.click(screen.getByRole('button'))
    expect(mockHistoryGoBack).toBeCalledTimes(1)
  })
})
```

**Hope this post help you a bit XD.**
**Happy coding!**

### Reference
https://itnext.io/react-redux-integration-tests-with-jest-enzyme-df9aa6effd13