4

I'm testing if my components render with Redux successfully with React Testing Library. I'm having trouble having my utility component to pass the renderWithRedux test. This is my App component.

function App() {
return (
    <>
        <Router>
            <NavBar />
            <div className="container">
                <Switch>
                    <Route exact path='/' component={Home}/>
                    <AuthRoute exact path='/login' component={Login} />
                    <AuthRoute exact path='/signup' component={Signup} />
                    <Route exact path='/users/:handle' component={UserProfile} />
                    <Route exact path='/users/:handle/post/:postId' component={UserProfile} />
                </Switch>
            </div>
        </Router>
    </>
);

}

Here is my AuthRoute utility component.

const AuthRoute = ({ component: Component, authenticated, ...rest }) => (
      // if authenticated, redirect to homepage, otherwise redirect to signup or login
        <Route
        {...rest}
        render={(props) =>
          authenticated === true ? <Redirect to='/' /> : <Component {...props} />
        }
      />
    );

AuthRoute.test.js

const renderWithRedux = () => render(
    <Provider store={myStore}>
        <AuthRoute />
    </Provider>
);

it('renders with Redux', () => {
    const {} = renderWithRedux(<AuthRoute />);
});

I've attempted the solutions from Invariant failed: You should not use <Route> outside a <Router>, but to no avail. I appreciate any help, thank you.

ln09nv2
  • 965
  • 4
  • 19
  • 35

3 Answers3

9

Render the component under test into a router

import { MemoryRouter } from 'react-router-dom';

const renderWithRedux = ({ children }) => render(
    <Provider store={myStore}>
        {children}
    </Provider>
);

it('renders with Redux', () => {
    const {} = renderWithRedux(
      <MemoryRouter>
        <AuthRoute />
      </MemoryRouter>
    );
});
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks, but I received `Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.` – ln09nv2 Aug 20 '20 at 22:29
  • @kennjamin in your `renderWithRedux` did you really intend to wrap whatever component was passed to it in a test (*i.e. render the children passed to it*)? I updated my answer. – Drew Reese Aug 20 '20 at 22:50
  • It worked! You're right, I should had changed wrapping the `App` component to instead wrap with children. Thank you! – ln09nv2 Aug 20 '20 at 23:08
2

Just like the Provider to wrap redux things you have to wrap your components with routes using MemoryRouter for the tests.

import { MemoryRouter } from 'react-router';

Sheldon Oliveira
  • 915
  • 9
  • 15
  • 1
    Thanks, but I received `Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.` – ln09nv2 Aug 20 '20 at 22:29
1

Basically, you have two wrapper elements. It should go something like this, for example, renderWithReduxWrapp => renderWithRouter => YourTestingComponent.

I had a similar issue when trying to test Button render (which has a Link) depending on props, and was able to solve it by creating some helper functions.

Here is the example:

This is the main component, UserCard.js, which renders user data from redux, and only shows a button if withButton props is passed.

import React from "react";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";

const CardComponent = ({ withButton }) => {
 const userInfo = useSelector((state) => getUserSelector(state));
  return (
   <div>
    <div>{userInfo}</div>
    {withButton && (
     <Link to="/settings" className="button-link">
      <Button block>EDIT CONTACT INFO</Button>
     </Link>
    )}
   </div>
  );
 };
export default CardComponent;

This is a CardComponent.test.js file. First, you need to add these lines of code

const ReduxWrapper = ({ children }) => {
 <Provider store={store}>{children} </Provider>;
}
const AppWrapper = ({ children }) => (
 <BrowserRouter>
  <ReduxWrapper>{children}</ReduxWrapper>
 </BrowserRouter>
);
const renderWithRouter = (ui, { route = '/' } = {}) => {
 window.history.pushState({}, 'Test page', route);
 return render(ui, { wrapper: AppWrapper });
};

After that, you need to start your test with renderWithRouter instead of just render method.

it('should render settings button if prop withButton is passed', () => {
 renderWithRouter(<CardComponent withButton />, { wrapper: ReduxWrapper });
 // apply you code here. I only needed to check if the button is renederd or not.
 const settingsButton = screen.queryByText(/edit contact info/i);
 expect(settingsButton).toBeInTheDocument();
});