5

I am trying to write unit test for the React functional component which has router hooks useLocation() like below.

//index.js
function MyComponent(props) {
  const useQuery = () => new URLSearchParams(useLocation().search);
   const status = useQuery().get('status');

  if (status === 'success') {
    return <ShowStatus message="OK" secret={props.secret} />;
  } else {
    return <ShowStatus message="NOT OK" secret={props.secret} />;
  }
}

//index.spec.js
describe('Test MyComponent', () => {
  it('should send OK when success', () => {
     sinon.stub(reactRouter, 'useLocation').returns({
        search: {
            status: 'success'
        }
     });
     const props = { secret: 'SECRET_KEY' };
     const wrapper = enzyme.shallow(<MyComponent.WrappedComponent {...props}/>);

     expect(wrapper.type()).to.have.length(MyComponent);
     expect(wrapper.props().message).to.equal('OK');
     expect(wrapper.props().secret).to.equal(props.secret);
  });

  it('should send NOT OK when error', () => {
     sinon.stub(reactRouter, 'useLocation').returns({
        search: {
            status: 'error'
        }
     });
     const props = { secret: 'SECRET_KEY' };
     const wrapper = enzyme.shallow(<MyComponent.WrappedComponent {...props}/>);

     expect(wrapper.type()).to.have.length(MyComponent);
     expect(wrapper.props().message).to.equal('NOT OK);
     expect(wrapper.props().secret).to.equal(props.secret);
  });
});

Even I am stubbing useLocation I am getting error

TypeError: Cannot read property 'location' of undefined
at useLocation (node_modules\react-router\modules\hooks.js:28:10)

I am trying to test ShowStatus component is render with right props based on the query param.

Any suggestion/help is appreciated.

Update: I am noticing even though I am importing from react-router-dom in both prod and test code. I am seeing prod one is taking from react-router.

αƞjiβ
  • 3,056
  • 14
  • 58
  • 95

1 Answers1

2

Use MemoryRouter component wraps your component is better than stub useLocation hook.

keeps the history of your “URL” in memory (does not read or write to the address bar). Useful in tests and non-browser environments like React Native.

We can provide the "URL" to your component under test via initialEntries props.

index.tsx:

import React from 'react';
import { useLocation } from 'react-router-dom';

export function ShowStatus({ message, secret }) {
  return <div>{message}</div>;
}

export function MyComponent(props) {
  const useQuery = () => new URLSearchParams(useLocation().search);
  const status = useQuery().get('status');

  if (status === 'success') {
    return <ShowStatus message="OK" secret={props.secret} />;
  } else {
    return <ShowStatus message="NOT OK" secret={props.secret} />;
  }
}

index.test.tsx:

import { mount } from 'enzyme';
import React from 'react';
import { MemoryRouter } from 'react-router';
import { MyComponent, ShowStatus } from './';

describe('MyComponent', () => {
  it('should send OK when success', () => {
    const props = { secret: 'SECRET_KEY' };
    const wrapper = mount(
      <MemoryRouter initialEntries={[{ search: '?status=success' }]}>
        <MyComponent {...props} />
      </MemoryRouter>
    );
    expect(wrapper.find(ShowStatus).props().message).toEqual('OK');
    expect(wrapper.find(MyComponent).props().secret).toEqual(props.secret);
  });

  it('should send NOT OK when error', () => {
    const props = { secret: 'SECRET_KEY' };
    const wrapper = mount(
      <MemoryRouter initialEntries={[{ search: '?status=error' }]}>
        <MyComponent {...props} />
      </MemoryRouter>
    );

    expect(wrapper.find(ShowStatus).props().message).toEqual('NOT OK');
    expect(wrapper.find(MyComponent).props().secret).toEqual(props.secret);
  });
});

test result:

 PASS  examples/59829930/index.test.tsx (8.239 s)
  MyComponent
    ✓ should send OK when success (55 ms)
    ✓ should send NOT OK when error (8 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 index.tsx |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        9.003 s

package versions:

"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"jest": "^26.6.3",
"react": "^16.14.0",
"react-router-dom": "^5.2.0",
Lin Du
  • 88,126
  • 95
  • 281
  • 483