Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add feature to allow mocking useRouter in Next.js for component testing #14707

Closed
MindaugasMateika opened this issue Jun 8, 2022 · 10 comments
Assignees

Comments

@MindaugasMateika
Copy link
Contributor

If the component uses useRouter() hook, tests will fail because the hook will be null/undefined.

Jest has jest.spyOn to mock it (reference - https://jamespotz.github.io/blog/how-to-mock-userouter). It would be helpful to have something similar here.

@pavelfeldman
Copy link
Member

Could you provide a complete example of what you'd like to achieve? Note that in most cases, putting all the mocking / init code into the playwright/index.js would be sufficient.

@MindaugasMateika
Copy link
Contributor Author

MindaugasMateika commented Jun 8, 2022

Given component

import { useRouter } from 'next/router'

const LanguageSwitch = () => {
  const router = useRouter()
  const { locales, locale: activeLocale } = router
  const otherLocales = locales?.filter(locale => locale !== activeLocale)

  return (
    // component implementation which uses router values
  )
}

export default LanguageSwitch

the test

test('renders language switch', async ({ mount }) => {
  const component = await mount(<LanguageSwitch />)
})

will result in:

    undefined: TypeError: Cannot destructure property 'locales' of 'router$1' as it is null.

What I would like to do, is mock what router would return in the test:

test('renders language switch', async ({ mount }) => {
  // mock router locales values for example so it would render component with those values
  const component = await mount(<LanguageSwitch />)
})

With Jest, it seems is possible to mock what the router would return on test by test basis (like in blog example I posted earlier)

@pavelfeldman
Copy link
Member

Once the patch above lands, you should be able to do the following:

// index.js
import router from 'next/router';
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';

beforeMount(async ({ hooksConfig }) => {
  // Before mount, redefine useRouter to return mock value from test.
  router.useRouter = () => hooksConfig.router;
});
// test.spec.jsx
// Pass mock value from test into `beforeMount`.
await mount(<App></App>, {
  hooksConfig: {
    route: {
      query: {page: 1, per_page: 10},
      asPath: '/posts'
    }
  }
});

@pavelfeldman
Copy link
Member

pavelfeldman commented Jul 12, 2022

I'll close it, but please feel free to reopen (file new) if there are outstanding issues.

@a-vershinin
Copy link

a-vershinin commented Nov 30, 2022

@pavelfeldman
Hi. Could you provide more complex example how to mock any modules in components. The example which was present before doesn't work with custom react hooks and useRouter (NextApp)

@albertodeago
Copy link

As a-vershinin I'm interested in this too, I don't really understand how hooks should be mocked.

Also, is there an example to make component-testing work with the react-router?

Thanks for the work you are doing, I'm approaching now to component-testing and it looks very promising

@jhancock532
Copy link

jhancock532 commented Jan 6, 2023

+1 to the above, I'd also love to see how to get component testing working with useRouter from Next.js

Update: useRouter can be mocked via the next-router-mock library in the following manner:

import { test, expect } from '@playwright/experimental-ct-react';
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-12';
import YourComponent from 'components/YourComponent';

// N.B. make sure to import the MemoryRouterProvider from the next-12 folder,
// the generic import didn't work for me.

test('it works', async ({ mount }) => {
    const component = await mount(
        <MemoryRouterProvider url="/">
            <YourComponent />
        </MemoryRouterProvider>
    );
});

See the next-router-mock documentation for info on what features are supported.

@scottlet
Copy link

scottlet commented Jan 6, 2023

IMHO mocking is essential for component tests - or we are stuck with only being to test components that render purely on props passed in - hooks are a major part of react development these days and not being able to mock a hook before tests is a big issue for us.

@harrison-carter
Copy link

Once the patch above lands, you should be able to do the following:

// index.js
import router from 'next/router';
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';

beforeMount(async ({ hooksConfig }) => {
  // Before mount, redefine useRouter to return mock value from test.
  router.useRouter = () => hooksConfig.router;
});
// test.spec.jsx
// Pass mock value from test into `beforeMount`.
await mount(<App></App>, {
 hooksConfig: {
   route: {
     query: {page: 1, per_page: 10},
     asPath: '/posts'
   }
 }
});

How does this work when mocking named exports?
The above example covers mocking a default export of router but useRouter as a hook is not a default export.
Screenshot 2023-01-06 at 11 40 29

With a different example of mocking useContext from react, how we would we overcome mocking an import and satisfying TypeScript?
Screenshot 2023-01-06 at 11 38 08

@bonchuk
Copy link

bonchuk commented Nov 3, 2023

Once the patch above lands, you should be able to do the following:

// index.js
import router from 'next/router';
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';

beforeMount(async ({ hooksConfig }) => {
  // Before mount, redefine useRouter to return mock value from test.
  router.useRouter = () => hooksConfig.router;
});
// test.spec.jsx
// Pass mock value from test into `beforeMount`.
await mount(<App></App>, {
 hooksConfig: {
   route: {
     query: {page: 1, per_page: 10},
     asPath: '/posts'
   }
 }
});

can you provide the same example but for the useParams() from the react-router-dom please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants