Simulating the useRouter function from the next/navigation (App Router) library in Cypress

I'm currently conducting a Cypress test on my component. This specific component utilizes the useRouter hook from next/navigation within a button to navigate back to another page.

import { useRouter } from "next/navigation";
import { VStack, Heading, Text, Button, Box, HStack } from "@chakra-ui/react";

const ForgotPassword = () => {
  const [usersEmails, setUsersEmails] = useState<String[]>([]);
  const [email, setEmail] = useState("");
  const [error, setError] = useState(false);


  const router = useRouter()

  useEffect(() => {
    const fetchEmails = async () => {
      const { error, data } = await supabase.from("users").select("email");

      if (error) {
        console.log(error);
      } else {
        const emailArray = data.map((user) => user.email);
        setUsersEmails(emailArray);
      }
    };

    fetchEmails();
  }, [supabase]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

  };

  return (
    <HStack h="100vh">
    <Box>

    <VStack w="full" maxW="md">
      <Heading as="h3" size="lg" color="#006bb2" alignSelf="flex-start">
        Forgot Password
      </Heading>
      <Text mb="15px">
        Enter your email and we&#34;ll send a link to reset your password
      </Text>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          id="email"
          name="email"
          placeholder="<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="54312c3539243831143139353d387a373b39">[email protected]</a>"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        {error && (
          <Text mt="5px" color="red">
            We cannot find your email
          </Text>
        )}

        <button>
          Submit
        </button>
      </form>

      <Button
        leftIcon={<ArrowBackIcon />}
        color="gray.500"
        _hover={{ color: "#006bb2", cursor: "pointer" }}
        variant="link"
        onClick={() => router.push("/login")}
      >
        Back to Login
      </Button>
    </VStack>
    </Box>
    </HStack>

  );
};

export default ForgotPassword;

Upon mounting the component, I encountered an error in Cypress:

(uncaught exception)Error: invariant expected app router to be mounted

The aforementioned error originates from the application code, not Cypress. invariant expected app router to be mounted

If I remove the "import useRouter" line, comment out "const router = useRouter()", and eliminate "router.push("/login")", then the component successfully mounts without any errors from Cypress.

I attempted the next/router & next/link solution mentioned in Cypress docs

import React from 'react'
import ForgotPassword from './page'
import Router from 'next/router'


describe('Testing the forgot password component', () => {
  context('stubbing out `useRouter` hook', () => {
    let router
    
    beforeEach(() => {
      router = {
        push: cy.stub().as('router:push')
      }

      cy.stub(Router, 'useRouter').returns(router)
    })

    it('checks if user email exits in db', () => {
      cy.mount(<ForgotPassword />)

      cy.get('input[name="email"]').type("<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dbb1b4b3b5bfb4be9bbea3bab6abb7bef5b8b4b6">[email protected]</a>");

      // cy.get('button').click()

      // cy.get('@routerBack').should((mock) => {
      //   expect(mock).to.have.been.calledOnce
      // })
    })
  })

})

Despite attempting these adjustments, Cypress continues to throw the same error "(uncaught exception)Error: invariant expected app router to be mounted," resulting in the failure to mount the component.

To address this issue, I modified the line import Router from 'next/router' to

import Router from 'next/navigation'
, subsequently transforming the Cypress error into:

Trying to stub property 'useRouter' of undefined

Due to this error occurring during a before-each hook, the remaining tests in the current suite are being skipped: stubbing out useRouter hook

Answer №1

To fix the uncaught exception

Error: invariant expected app router to be mounted
, wrapping the component in
<AppRouterContext.Provider value={router}>
is necessary.

In addition to the major fix, there are some secondary points to address:

  • The inconsistency between the usage of 'next/router' in the test and "next/navigation" in the component should be corrected for consistency.

  • When stubbing useRouter from the Router object, ensure you name the default import as follows:
    with:

    import * as Router from "next/navigation"

    not:
    import Router from "next/navigation"

    This adjustment resolves the error

    Trying to stub property 'useRouter' of undefined


  • The alias used in the calledOnce assertion should match the name used in the stub.

Below is a test for the NextJs sample component

component

'use client'
 
import { useRouter } from 'next/navigation'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  )
}

test

import React from 'react'
import { AppRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'
import * as Router from "next/navigation";
import Page from './example-client-component.js'

it('stubs the router', () => {
  const router = {
    push: cy.stub().as('router:push')
  }
  cy.stub(Router, 'useRouter').returns(router)

  cy.mount(
    <AppRouterContext.Provider value={router}>
      <Page />
    </AppRouterContext.Provider>
  )

  cy.get('button').click()

  cy.get('@router:push').should((mock) => {
    expect(mock).to.have.been.calledOnce
  })
})

test log

https://i.sstatic.net/nloMa7PN.png

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Cannot locate metadata for entity: Node.js TypeScript TypeORM

I've recently ventured into the world of typescript + typeorm and have been grappling with a particular issue for quite some time now. Despite scouring numerous github issues, I haven't been able to pinpoint the root cause. Here's how my pr ...

How can I dynamically update a React/Next.js page as the server completes different stages of a complex action?

Currently, my aim is to develop a single-page application that relies on one major back-end process. This procedure is initiated by a singular string input. The various stages involved and the outcomes are as follows: It takes about 20ms to display/render ...

Tips for enlarging the font size of a number as the number increases

Utilizing the react-countup library to animate counting up to a specific value. When a user clicks a button, the generated value is 9.57, and through react-counter, it visually increments from 1.00 to 9.57 over time. Here's the code snippet: const { ...

Dealing with intricate JSON data from an API that requires mapping to a TypeScript class

Having trouble extracting collections from a complex JSON response in an API to map to local arrays in TypeScript using Angular-v5 and HTTPClient. While I can successfully consume the API and access properties of a TypeScript class called PolicyDetail, I a ...

The expected rendering of column headers was not achieved following the refactoring of <Column />

After making changes, the header is not rendering properly and I cannot see the "Product ID" header for the column: // DataTable.tsx // React Imports import React, { useState } from 'react'; // PrimeReact Imports import { DataTable as DT } from ...

Take out a specific element from an array consisting of multiple objects

I have a specific array structure and I need to remove elements that match a certain criteria. Here is the initial array: const updatedUsersInfo = [ { alias: 'ba', userId: '0058V00000DYOqsQAH', username: '<a href=" ...

Is it possible to direct users to varying links based on their individual status?

import React from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import Link from "next/link"; import { cn } from "@/lib/utils"; import { FaCircleChec ...

Dividing a collection of URLs into smaller chunks for efficient fetching in Angular2 using RxJS

Currently, I am using Angular 2.4.8 to fetch a collection of URLs (dozens of them) all at once. However, the server often struggles to handle so many requests simultaneously. Here is the current code snippet: let collectionsRequests = Array.from(collectio ...

Tips for handling numerous observables in Angular 7

I am working on an Angular 7 application that deals with a total of 20 sensor data. My goal is to receive data from a selected sensor every 5 seconds using observables. For example: var sensorId = ""; // dynamically chosen from the web interface var senso ...

Cleaning up with useEffect in the newest version of React

Recently, I made the switch to using locomotive scroll with next.js. However, after upgrading to react v18, my clean up stage has suddenly stopped working. Can someone shed some light on why this might be happening? useEffect(() => { let scroll; import( ...

What causes the cursor in an editable div to automatically move to the front of the div?

<div className="min-w-[600px] min-h-[36.8px]" > <div id={`editableDiv-${Object.keys(item)}-${index}`} className="p-3" contentEditable suppressContentEditableWarning onInput={(e) => onChange(e)} > ...

Using Cypress for drag and drop functionality within shadow DOM

I encountered an issue in cypress with drag and drop functionality within the shadow dom. My attempt to perform a drag event is as follows: cy.get(".shadow-app>div>div:nth-child(1)", { includeShadowDom: true }).trigger("dragstart&q ...

What is the reasoning behind Typescript's belief that the symbol 'name' is consistently present?

When working with Typescript, I've noticed that my code is sometimes accepted by the compiler even when I mistakenly write name instead of someObject.name. To test this behavior, I compiled a file with just console.log(name) and surprisingly, the Typ ...

Exploring Type Guards and fat arrow functions in Typescript

Is this supposed to compile correctly? I'm encountering an error that says "Property 'hello' does not exist on type 'object'." at the highlighted line. Interestingly, I can access g.hello outside the fat arrow function without any ...

Upon upgrading NextJS, the error "previewData is not a function" has been encountered

I encountered an issue with my NextJS and Sanity.io blog project after updating NextJS from version 13.2.3 to 13.4.2. Upon running the project, I received the following error message: Error: (0 , next_headers__WEBPACK_IMPORTED_MODULE_1__.previewData) is ...

Having trouble accessing an Angular app through express and Heroku?

I'm fairly new to express, and I have an Angular single-page application that I'm attempting to deploy on Heroku at the following link: Unfortunately, all I see is a blank screen. This happens when I run my server.js file on port 8000 as well. H ...

Is it Observable or Not? Typescript Typehint helping you determine

I'm facing an issue while writing a function with correct typehints. It seems to work fine with explicit type hinting but breaks down when trying to use automatic type hinting. Can someone please help me identify the error in my code and suggest how I ...

Leveraging TypeScript unions within functions to handle and throw errors

As a newcomer to TypeScript, I've encountered an odd error that I need help with. I have various objects sending data to the server and receiving fresh data back of the same object type. These objects use a shared method for sending the data, so I ap ...

Unable to pass a component property to a styled Material-UI Button

I have customized a MUI Button: const SecondaryButton = styled(Button)<ButtonProps>(({ theme }) => ({ ... })); export default SecondaryButton; When I try to use it like this: <label htmlFor="contained-button-file"> <input ...

Tips on navigating an array to conceal specific items

In my HTML form, there is a functionality where users can click on a plus sign to reveal a list of items, and clicking on a minus sign will hide those items. The code structure is as follows: <div repeat.for="categoryGrouping of categoryDepartm ...