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

jest example fails with next/link #1827

Closed
remy opened this issue Apr 28, 2017 · 23 comments · Fixed by #6578
Closed

jest example fails with next/link #1827

remy opened this issue Apr 28, 2017 · 23 comments · Fixed by #6578

Comments

@remy
Copy link
Contributor

remy commented Apr 28, 2017

Example repo: https://github.com/remy/next-examples/tree/master/with-jest#fails-with

The jest example in the examples directory is too simple, so it quickly falls over when I started testing a slightly larger app (than a single page with some jsx).

How do we get snapshot testing to work with next? Can we have jest tell next (somehow???) that prefetch shouldn't happen?

@remy
Copy link
Contributor Author

remy commented Apr 28, 2017

Hmm, on the back of this, prefetch is supposed to be production only - so is jest running next.js in production mode?

Note: I tried NODE_ENV=dev npm test and it fails with the same result You should only use "next/router" inside the client side of your app.

@kunal-mandalia
Copy link

+1. Echoed too on #1204

@arunoda
Copy link
Contributor

arunoda commented May 2, 2017

I think we need to fix this bug. On it.

@remy
Copy link
Contributor Author

remy commented May 24, 2017

@arunoda did you get anywhere on this? I don't know how to get around this at all, so my application is currently failing all tests :(

@roopemerikukka
Copy link

roopemerikukka commented Jun 5, 2017

Not sure if this is related, but I'm getting the same error if I'll try to test container that uses Router.push() function directly. I haven't really found a way to mock it.

@arunoda
Copy link
Contributor

arunoda commented Jun 7, 2017

Yes. When we use next/router or next/link with prefetch, it'll fail inside JEST.
That's because there's no actual router instance.

We need to find a way to mock this.
Something like this would work.
Haven't tested.

import Router from `next/router`
const mockedRouter = { push: () => {} }
Router.router = mockedRouter

@roopemerikukka
Copy link

@arunoda that seems to work. Thanks!

@kgoggin
Copy link

kgoggin commented Aug 14, 2017

For anyone else landing here trying to make this work with a <Link prefetch/> in storybook, I used @arunoda's example but had to add a prefetch noop on the mocked router as well.

@evantahler
Copy link
Contributor

With next 3.x, even without prefetch, <Link /> causes this error

@alexedev
Copy link

@kgoggin could you please show your code? did not really understand what you mean by noop and when you put it.

@alexedev
Copy link

@kgoggin it seems that I understood.
for anybody using prefetch:

import Router from 'next/router'
const mockedRouter = { push: () => {}, prefetch: () => {} }
Router.router = mockedRouter

@alexedev
Copy link

alexedev commented Aug 21, 2017

ps when I mock it like this in storybook config for stories to work then I get Uncaught TypeError: Cannot read property 'then' of undefined at Link.linkClicked (link.js:123) because onClick calls linkClicked which has .then(...) after calling Router.router.push. how can we mock this one?

@alexedev
Copy link

alexedev commented Aug 21, 2017

in the end for storybook (not for jest) I mocked it like this in config:

const actionWithPromise = () => {
  action('clicked link')();
  // we need to return promise because it is needed by Link.linkClicked
  return new Promise((resolve, reject) => reject());
};

const mockedRouter = {
  push: actionWithPromise,
  replace: actionWithPromise,
  prefetch: () => {},
};

Router.router = mockedRouter;

for jest I just have router fully ignored:
jest.mock('next/router');
though it may be not enough if Link onClick needs to be simulated

@timneutkens timneutkens changed the title jest example fails with link jest example fails with next/link Sep 3, 2018
@kylemh
Copy link
Contributor

kylemh commented Sep 3, 2018

Anybody got advice on mocking withRouter within Storybook and Jest?

I tried withRouter = Component => props => <Component {...props} router={mockedRouter} /> with no success.

@ssylvia
Copy link

ssylvia commented Sep 12, 2018

@kylemh I ran into this and was able to get it working by wrapping the story in a HOC that provides a custom router object in it's context. It looks like next/router relies on the legacy context API so this will probably break in later versions. Here's my mock files:

const { Component } = require('react');
const Router = require('next/router').default;
const { action } = require('@storybook/addon-actions');
const PropTypes = require('prop-types');

const actionWithPromise = () => {
  action('clicked link')();
  return new Promise((resolve, reject) => reject());
};

const mockedRouter = {
  push: actionWithPromise,
  replace: actionWithPromise,
  prefetch: () => {},
  route: '/mock-route',
  pathname: 'mock-path',
};

Router.router = mockedRouter;

const withMockRouterContext = (mockRouter) => {
  class MockRouterContext extends Component {
    getChildContext() {
      return {
        router: Object.assign(mockedRouter, mockRouter),
      };
    }
    render() {
      return this.props.children;
    }
  }

  MockRouterContext.childContextTypes = {
    router: PropTypes.object,
  };

  return MockRouterContext;
};

module.exports.mockedRouter = mockedRouter;
module.exports.withMockRouterContext = withMockRouterContext;

Then just wrap your story with a MockRouter:

import { withMockRouterContext } from 'test-utils/react/nextjs/router';

const MockRouter1 = withMockRouterContext([extendDefaultMockRouter]);

stories.add(
  'Default',
  () => (
    <MockRouter1>
      <ActiveLink href="test-active">Test</ActiveLink>
    </MockRouter1>
  )
);

@kylemh
Copy link
Contributor

kylemh commented Sep 21, 2018

@ssylvia looks like @nickluger got a convo going in #5205

Yours worked. I also added a bit more logic and used it as a global decorator so as to not interfere with the withInfo decorator.

Will be migrating to storybook@4 by the end of October - interested to see how this tale progresses.

sumitparakh added a commit to sumitparakh/front-end that referenced this issue Nov 8, 2018
@joaovieira
Copy link

Would be good to see this fixed after almost 2 years. This is a barrier to anyone coming to Next.js as they'll bump into this pretty soon when they try to mount a component with <Link prefetch> - which you guys recommend.

@mrmartineau
Copy link

thanks @ssylvia, I used a tweaked version of what you suggested:

/* tslint:disable */

import { Component } from 'react';
import Router from 'next/router';
import { action } from '@storybook/addon-actions';
import PropTypes from 'prop-types';

const actionWithPromise = () => {
  action('clicked link')();
  return new Promise((_, reject) => reject());
};

const mockedRouter = {
  push: actionWithPromise,
  replace: actionWithPromise,
  prefetch: () => {},
  route: '/mock-route',
  pathname: 'mock-path',
};

// @ts-ignore
Router.router = mockedRouter;

const withMockRouterContext = mockRouter => {
  class MockRouterContext extends Component {
    public getChildContext() {
      return {
        router: { ...mockedRouter, ...mockRouter },
      };
    }
    public render() {
      return this.props.children;
    }
  }

  // @ts-ignore
  MockRouterContext.childContextTypes = {
    router: PropTypes.object,
  };

  return MockRouterContext;
};

export const StorybookRouterFix = withMockRouterContext(mockedRouter);

Usage

import { StorybookRouterFix } from '/utils/storybook/StoryRouterFix';

// .... 

storiesOf('ComponentThatHasALink', module)
  .add('ComponentThatHasALink', () => (
    <StorybookRouterFix>
      <ComponentThatHasALink
      />
    </StorybookRouterFix>
  ));

@helderberto
Copy link

If you only want to show the component without the Router functionalities, try this:

const Router = {}
const mockedRouter = { push: () => {}, prefetch: () => {} }
Router.router = mockedRouter

timneutkens added a commit that referenced this issue Mar 8, 2019
Fixes #1827 

This doesn't affect integration tests as they'd use `next build` which forces production mode. Development forces `development`.
@wilson-alberto-kununu
Copy link

Leaving my two cents, based on previous replies:

const actionWithPromise = () => new Promise((_, reject) => reject());

jest.mock('next/router', () => ({
  push: actionWithPromise,
  replace: actionWithPromise,
  prefetch: () => {},
  route: '/mock-route',
  pathname: 'mock-path',
}));

@DannyJoris
Copy link

DannyJoris commented Aug 6, 2019

The withMockRouterContext solution works well when the router in the original component is implemented with withRouter(). Is there a way to make it work if you're using router as a React Hook with useRouter()?

Edit: found this: #7479

@andrastothtw
Copy link

andrastothtw commented Aug 21, 2019

Adding to @wilson-alberto-kununu 's example this is what we cooked up in the team when your component's behavior depends on values coming from router values (and why else would you use withRouter?), for example you need to access a parameter in query string and you want to write multiple unit tests.

// (In your the test file of `<Something />` component:

async function createComponent(queryStringParams = {}) {
  jest.doMock('next/router', () => ({
    withRouter: component => {
      component.defaultProps = {
        ...component.defaultProps,
        router: {
          pathname: 'something',
          query: queryStringParams
        },
      };

      return component;
    },
  }));

  const { Something } = await import('./something'); // 👈 please note the dynamic import 

  return mount(<Something />);
}

@tkmcmaster

This comment has been minimized.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.