MDalt
MDalt

Reputation: 1801

Testing a sub-component with a Link: '<Link>s rendered outside of a router context cannot navigate.'

I have a button component that creates a react-router Link element. It also allows an onClick function to be passed in for additional functionality (e.g. sending a Google Analytics event).

I have included this component in a parent, like so:

export default class Page extends Component {
   const doSomething = () => {
    //do a thing to test here
   }

   return (
      <div>
         <Button
            onClickFn{() => doSomething()}
            linkToUrl='/other/page' //this creates a <Link> inside the button
          />
      </div>
   )
}

Problem comes when I want to test that doSomething is being triggered correctly. I have used Enzyme mount to create the test Page component including the button. When I simulate a click I get the following error

 '<Link>s rendered outside of a router context cannot navigate.'

because the Link in the button has no context. Is there a way of mocking this or preventing the error from showing? Or is there a better way of testing this functionality?

Upvotes: 3

Views: 2372

Answers (2)

Dennis
Dennis

Reputation: 59449

Building on top of Paul's answer, here's a more detailed example for testing the onClick of a Button (or its Link child to be more precise). The example uses the testing libraries mocha (BDD test runner), chai (BDD assertions), enzyme (React testing utility), and sinon (test doubles).

import React from 'react';
import { Router, Route } from 'react-router';
import { createMemoryHistory } from 'history';

import MyCustomPage from '/.index';

describe('MyCustomPage', function(){
  it('stores data when clicking the link', function() {
    // Arrange
    const Page = () => (
      <MyCustomPage foo="foo" bar="bar" />
    );

    const container = enzyme.mount(
      <Router history={createMemoryHistory()}>
        <Route path="/" component={Page} />
      </Router>
    );

    // Act
    container.find('#my-link').simulate('click');

    // Assert
    expect(sessionStorage.setItem).to.have.been.calledWith('key', 'value');
  });
});

Upvotes: 2

Paul S
Paul S

Reputation: 36787

In your test, you will need to render the component within a <Router>. You can take a look at the tests for the <Link> component for examples on how to do this.

The basic idea is to create a memory history instance, pass that to the <Router>, and then render the <Link> inside of a <Route> passed to that. It sounds a bit involved, but it is fairly simple.

import { createMemoryHistory } from 'history'

it('clicks', () => {
  const history = createMemoryHistory()
  const App = () => (
    <Router history={history}>
      <Route path='/' component={Page} />
    </Router>
  )
})

Upvotes: 3

Related Questions