Skip to content

Mock router to test component using Jest #136

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

Open
gabrielgian opened this issue Oct 24, 2017 · 24 comments
Open

Mock router to test component using Jest #136

gabrielgian opened this issue Oct 24, 2017 · 24 comments

Comments

@gabrielgian
Copy link

gabrielgian commented Oct 24, 2017

I want to test my components with Snapshots and am using Jest to do so. Since my component is using withRoute HOC, both my component and somewher in HOC require router and all its functions in their in the contexts.

Is there a easy way to create an instance of router or mock it?

Here is a sample of my code:

const initialState = store.getState();
const router = {}; // ???

test('Login changes', () => {
  const wrapper = shallow(<Login />, {
    context: { store: mockStore(initialState), router }
  });
  expect(wrapper.dive()).toMatchSnapshot();
}); 
@taion
Copy link
Contributor

taion commented Oct 24, 2017

We use a mock object where we replace all the functions with sinon.spy(). If you're using Jest's mocking, then you can just jest.fn(). You may need trivial implementations of createHref and createLocation, though.

Does Jest have like "smart" mocks that use object proxies or anything?

@jquense
Copy link
Member

jquense commented Oct 24, 2017

jest will auto mock something intelligently i think based on module exports. e.g. jest.mock('found/router')

@taion
Copy link
Contributor

taion commented Oct 24, 2017

I don't think we export the object anywhere, though. We do export the prop type. Python has "smart" mocks that are essentially proxy objects with all methods auto-mocked. Does such a thing exist in the Node ecosystem?

@jquense
Copy link
Member

jquense commented Oct 24, 2017

i think jest is the best example of it, but it still a bit welded into jest itself. there might be some better optins tho now that Proxy is well supported

@gabrielgian
Copy link
Author

I was able to mock 'found' module by doing jest.mock('found'), but couldn't the router itself.

Following @taion comment the test passed (although it seems to me like a temporary solution) by defining router as:

const router = {
  push: jest.fn(),
  replace: jest.fn(),
  go: jest.fn(),
  createHref: jest.fn(),
  createLocation: jest.fn(),
  isActive: jest.fn(),
  matcher: {
    match: jest.fn(),
    getRoutes: jest.fn(),
    isActive: jest.fn(),
    format: jest.fn()
  },
  addTransitionHook: jest.fn()
};

@taion
Copy link
Contributor

taion commented Oct 24, 2017

I guess we could expose like a found/testing that has helpers for building mocks like that.

@gabrielgian
Copy link
Author

I can make a PR if you decide to do so. Just want to know which are the objects you want me to expose.

Another test related issue I faced was to get an initial state of found to initialize a mockStore configured by redux-mock-store. This issue might be addressed in #91, but instead of server rendering, I created a new Store only for testing. I am not sure if that is the best solution too.

@taion
Copy link
Contributor

taion commented Oct 24, 2017

Well, if you're trying to test state management, it might be easiest to use a server protocol and just actually instantiate Found.

@gabrielgian
Copy link
Author

No problems with Found instance so. TY again @taion .

@cmutzel
Copy link

cmutzel commented Nov 20, 2017

For those trying to test components that depend on router, here is what I ended up doing.

export function renderWithRouter(Component, props) {  
  const config = makeRouteConfig(
      <Route path='/'
        render={() => React.createElement(Component, props)}
      />
  );

  // Now make a request against the route matching this route.
  return getFarceResult({ // promise
    url: "/",
    routeConfig: config,
    render: createRender({})
  }).then(result => renderer.create(result.element));
}
...
it('snapshot', () => {
  // Catch a rejected promise by looking for a certain number of assertions
  expect.assertions(1);

  return renderWithRouter(LeftSidePanel).then(tree => {
    expect(tree.toJSON()).toMatchSnapshot()
  })
});

@taion
Copy link
Contributor

taion commented Nov 20, 2017

Okay, I think the thing to do here is to add a found/testing or found/test-utils module that wraps up some of these test utils for convenience.

@rosskevin
Copy link
Contributor

Along these lines, I'm trying to enzyme mount and need the router. Since I'm using a link, it fails with the simple spy-based router. So +1 for exposing a test utility.

@wkerswell-gresham
Copy link

Was there a fix for this? I have tried @gabrielgian suggestion of mocking the router object with with jest.fn() I was then faced with similar issues for farce which I also mocked but still having problems in the found/lib/createWithRouter.js:24 with found being undefined

@wkerswell-gresham
Copy link

@gabrielgian do you have an example you could share or something working?

@wkerswell-gresham
Copy link

So I ended up mocking the <Link/> component using jest. This fixed the error and produced an isolated component no dependency on the result of the link.

// Mock the nested "connected" components to avoid error with missing context.
jest.mock('found/lib/Link', () => 'link');

@taion
Copy link
Contributor

taion commented Apr 10, 2018

That makes sense to me.

@quantuminformation
Copy link

Guys, I want to test this component that is use in my app inside a withRouter( connect(

what is the best way to test it with as little boilerplate code?

  componentWillMount() {
    const { issueDetails, loadIssueDetails, historyTypes, loadLookupData, match } = this.props;

    if (match.params.issueId === 'create' && issueDetails === undefined) {
      if (!this.props.siteId) {
        this.props.history.push('/')

I would rather use mount, than doing something like this in the test

     <Route path='/'
        render={() => React.createElement(Component, props)}
      />
  );```

@taion
Copy link
Contributor

taion commented Mar 26, 2019

There's not a really good way to do this at the moment. I'm going to expose the router context as its own thing, and then it will be possible to inject a test router, but right now the best bet is to follow the paths above – either render a server/memory router.

Though if you're actually testing navigation, arguably it'd be best to use MemoryProtocol anyway rather than using a mock.

@taion
Copy link
Contributor

taion commented Apr 5, 2019

Okay, plan here is to add a dummyRouter export in found/lib/test-utils (and a Jest-specific version as well).

@MatthewMSaucedo
Copy link

MatthewMSaucedo commented Aug 21, 2019

dummyRouter sounds like a good solution!

having trouble implementing the above solutions so one a bit more simple is something to look forward to!

@MatthewMSaucedo
Copy link

As an update, I found it much easier to just make a MockRouter class with just the methods I needed. Then in a test,

const mockRouter = new MockRouter() as unknown as Router;

Worked fine for my use cases.

@renanmav
Copy link

Any update on this?

@caseybaggz
Copy link

For anyone who uses jest, this has been working for us:

Re anything that returns a <BaseLink /> error, just add this to your test file:

jest.mock('found/BaseLink');

Re anything context related that probably stems from useRouter, use this:

jest.mock('found/useRouter', () => {
  return function useRouter() {
    return {
      router: {
        addNavigationListener: jest.fn(),
        createHref: jest.fn(),
        createLocation: jest.fn(),
        go: jest.fn(),
        isActive: jest.fn(),
        matcher: {
          match: jest.fn(),
          getRoutes: jest.fn(),
          isActive: jest.fn(),
          format: jest.fn(),
        },
        push: jest.fn(),
        replace: jest.fn(),
        replaceRouterConfig: jest.fn(),
      },
      match: {
        context: undefined,
        location: {
          action: '',
          delta: 0,
          hash: '',
          index: 0,
          key: '',
          pathname: '',
          query: {},
          search: '',
          state: undefined,
        },
        params: {
          org: '',
          view: '',
        },
        route: {},
        routeIndices: [],
        routeParams: null,
        routes: [],
      },
    };
  };
});

@mos-adebayo
Copy link

mos-adebayo commented Nov 22, 2021

You can mock your react-router with the code below. Let's say all you needed in your component is Redirect from router-dom. You can put the code below before describe block

jest.mock("react-router-dom", () => ({ Redirect: jest.fn(() => null) }));

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

No branches or pull requests