My Navbar component relies on the useRouter function provided by nextjs/router in order to style the active links.
I'm trying to test this behavior using Cypress, but I'm unsure of how I'm supposed to organize it. Cypress doesn't seem to like getRoutePathname() and undefined is returned while within my testing environment.
Here's the component I'm trying to test:
import Link from 'next/link'
import { useRouter } from 'next/router'
function getRoutePathname() {
  const router = useRouter()
  return router.pathname
}
const Navbar = props => {
  const pathname = getRoutePathname()
  return (
    <nav>
      <div className="mr-auto">
        <h1>Cody Bontecou</h1>
      </div>
      {props.links.map(link => (
        <Link key={link.to} href={link.to}>
          <a
            className={`border-transparent border-b-2 hover:border-blue-ninja 
            ${pathname === link.to ? 'border-blue-ninja' : ''}`}
          >
            {link.text}
          </a>
        </Link>
      ))}
    </nav>
  )
}
export default Navbar
I have the skeleton setup for the Cypress component test runner and have been able to get the component to load when I hardcode pathname, but once I rely on useRouter, the test runner is no longer happy.
import { mount } from '@cypress/react'
import Navbar from '../../component/Navbar'
const LINKS = [
  { text: 'Home', to: '/' },
  { text: 'About', to: '/about' },
]
describe('<Navbar />', () => {
  it('displays links', () => {
    mount(<Navbar links={LINKS} />)
  })
})
                Since the original posting Cypress added some better documentation on component testing NextJs.
Specifically on the router Customize your Next.js Testing Experience this is the example (simplified)
If you add it to the custom nextMountWithStubbedRoutes() command, it can be used in any spec.
Cypress.Commands.add('nextMountWithStubbedRoutes', (component, options) => {
  const router = {
    route: '/',
    pathname: '/',
    query: {},
    asPath: '/',
    basePath: '',
    back: cy.stub().as('router:back'),
    forward: cy.stub().as('router:forward'),
    push: cy.stub().as('router:push'),
    reload: cy.stub().as('router:reload'),
    replace: cy.stub().as('router:replace'),
    isReady: true,
    ...(options?.router || {}),
  }
  return mount(
    <RouterContext.Provider value={router}>
      {component}
    </RouterContext.Provider>,
    options
  )
})
See caveat:
Unfortunately, there’s no such thing as a free lunch—adding these extra items to every mount will affect performance and introduce global state elements outside the bounds of your component. It’s up to you to decide whether these trade-offs are worth it based on your use case.
Given this warning, use specific nextMountWithStubbedRoutes() only with tests that need it.
Ideally, there'd be a provider for Next.js's useRouter to set the router object and wrap the component in the provider in mount. Without going through the code or Next.js supplying the documentation, here's a workaround to mock useRouter's pathname and push:
import * as NextRouter from 'next/router'
// ...inside your test:
const pathname = 'some-path'
const push = cy.stub()
cy.stub(NextRouter, 'useRouter').returns({ pathname, push })
I've added push because that's the most common use case, which you may also need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With