5

I'm using RedwoodJS which uses React with React Testing Library under the hood. I'm struggling to test a component (and all page components which have this component in the tree) because of the useLocation() hook.

When using a useLocation() hook inside my component, I need to wrap my component under test with a Router that mocks the browser location history to prevent the error Error: Uncaught [TypeError: Cannot read property 'pathname' of undefined].

However when I do that, the Navigation component is no longer fully rendered, so I can't test it... any ideas?

Navigation.js

//import statements

const renderListItems = (pathname) => {
  const NavigationItems = [{..},{..},{..}] // example

  return NavigationItems.map((item) => {
    const selected = pathname.indexOf(item.path) ? false : true
    return (
      <ListItem
        button
        key={item.text}
        onClick={() => {
          navigate(item.route)
        }}
        selected={selected}
      >
        <ListItemText primary={item.text} />
      </ListItem>
    )
  })
}

const Navigation = () => {
  const { pathname } = useLocation() // this is why I need to wrap the Navigation component in a router for testing; I'm trying to get the current pathname so that I can give a specific navigation item an active state.

  return (
    <List data-testid="navigation" disablePadding>
      {renderListItems(pathname)}
    </List>
  )
}

export default Navigation

Navigation.test.js

import { screen } from '@redwoodjs/testing'
import { renderWithRouter } from 'src/utilities/testHelpers'

import Navigation from './Navigation'

describe('Navigation', () => {
  it('renders successfully', () => {
    expect(() => {
      renderWithRouter(<Navigation />)
    }).not.toThrow()
  })
  it('has a "Dashboard" navigation menu item', () => {
    renderWithRouter(<Navigation />)
    expect(
      screen.getByRole('button', { text: /Dashboard/i })
    ).toBeInTheDocument()
  })
})

testHelpers.js

This is needed to prevent useLocation() inside Navigation.js from breaking the test.

import { Router, Route } from '@redwoodjs/router'
import { createMemoryHistory } from 'history'
import { render } from '@redwoodjs/testing'
const history = createMemoryHistory()

export const renderWithRouter = (Component) =>
  render(
    <Router history={history}>
      <Route component={Component} />
    </Router>
  )

Resulting error

Navigation › has a "Dashboard" navigation menu item

TestingLibraryElementError: Unable to find an accessible element with the role "button"

    There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole

    <body>
      <div />
    </body>
Snowman
  • 1,160
  • 1
  • 8
  • 24

1 Answers1

0

You can mock useLocation to return the pathname you want. This can apply to any function

Simple


//Put within testing file
jest.mock("router-package", () => ({
  ...jest.requireActual("router-package"),
  useLocation: () => ({
    pathname: "customPath/To/Return"
  })
}));

Detailed

You can create a helper function where you can pass the path(string) and it automatically mocks it for you as such

random.test.js

import { setUpPageRender } from 'src/utilities/testHelpers'
import Navigation from './Navigation'

describe('Navigation', () => {
  //Where we set up our custom path for the describe
  const render = setUpPageRender('/customPathForThisDescribe/Foo')

  it('renders successfully', () => {
    expect(() => {
      render(<Navigation />)
    }).not.toThrow()
  })
})

testHelpers.js

//Mocked Functions
jest.mock('router-package', () => ({
  __esModule: true,
  ...jest.requireActual('router-package'),
  useLocation: jest.fn(),
}))
import { useLocation } from 'router-package'


export const setUpPageRender = (location) => {
  useLocation.mockReturnValue(location)

  beforeEach(() => {
    jest.clearAllMocks()
  })

  return (component) => {
    return render( <Router history={history}>
      <Route component={Component} />
    </Router>)
  }
}
Jonathan Southern
  • 1,121
  • 7
  • 22