How to test a component that contains useLocation()?

I’m struggling to test a navigation menu component (and all my pages which have this navigation component in the tree - I use the navigation component in my main layout that wraps all my page contents) because of the useLocation() hook. I use that hook to get the current path of the page so that I can determine whether to give my navigation menu items an active state.

When using a useLocation() hook inside my component, in the test I need to wrap it 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 - I just get an empty document - so I can’t test it… is there a better way to test the Navigation component (and all pages that contain it)?

Navigation.js

//import statements, including MaterialUI components

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

  return NavigationItems.map((item) => {
    const selected = pathname.indexOf(item.path) ? false : true // active state is based on the pathname retrieved from useLocation()
    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>

@darryl-snow, just checking in. It’s been a while, do you still need help with this?

1 Like

Sorry I realize it’s probably more of a question relating to react testing library, but yes I still haven’t got it working. Perhaps I need to figure out how to get the test to navigate to a page that contains the component?

Probably not going to help with your testing issues, but just thought I’d mention that Redwood has <NavLink> for this exact purpose. https://redwoodjs.com/docs/redwood-router#active-links

But on to your issue.

I’m curious about your renderWithRouter function. A regular Redwood <Route> has this syntax <Route path="/" page={HomePage} name="home" /> and also, the <Router> doesn’t take a history prop. It looks like you might have copy/pasted from code that’s written to be used with react-router. Redwood has its own router, that isn’t compatible.