Search code examples
reactjsreact-router-domreact-testing-library

How to test a form with action redirection using react-testing-library


I added a search bar using a form and an input to my React app, and I would like to check that it redirects to the right page. I am using react-testing-library and react-router-dom.

Here is my component (simplified):

const SearchBar = () => (
  <form action={'/search'}>
    <input type="search" name="q" placeholder="search"></input>
  </form>
)

So I wrote this test to check that when I type something in the input, I am redirected to the /search page (which happens in my browser). I followed https://testing-library.com/docs/example-react-router/ and came up with this:

  it('redirects to search page', async () => {
    const history = createMemoryHistory();
    const header = render(
      <Router history={history}>
        <Switch>
          <Route exact path="/">
            <SearchBar />
          </Route>
          <Route path="/search">
            <div>this is the search page</div>
          </Route>
        </Switch>
      </Router>
    );

    const searchBar = header.getByPlaceholderText('search');
    fireEvent.change(searchBar, { target: { value: 'myQuery' } });
    fireEvent.submit(searchBar);
    expect(screen.getAllByText('this is the search page')).toBeInTheDocument();
  });

Unfortunately it fails with the following error:

    TestingLibraryElementError: Unable to find an element with the text: this is the search page. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <form
          action="/search"
        >
          <input
            name="q"
            placeholder="search"
            type="search"
          />
        </form>
      </div>
    </body>

      91 |     fireEvent.change(searchBar, { target: { value: 'myQuery' } });
      92 |     fireEvent.submit(searchBar);
    > 93 |     expect(screen.getAllByText('this is the search page')).toBeInTheDocument();
         |                   ^
      94 |   });
      95 | });
      96 |

      at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
      at node_modules/@testing-library/dom/dist/query-helpers.js:92:38
      at getAllByText (node_modules/@testing-library/dom/dist/query-helpers.js:127:15)
      at Object.<anonymous> (src/components/Header/Header.test.tsx:93:19)

From what I understand, I believe that submitting a form does not change the location of my router.

Is there a way to make the test pass without changing my code? Or should I forget relying on the action of the form and instead implement a custom onSubmit property that handles the redirection - which I could more easily test?

Thanks :)


Solution

  • The form action does not use React Router DOM but instead the native method.

    <form action={'/search'}>
    

    If you want to use React Router for this, consider using a React onSubmit handler. Then your original test should more or less work after integrating with your actual implementation.

    const SearchBar = () => {
      const history = useHistory();
    
      const handleSubmit = (event) => {
        // Prevent default native behavior of submit
        event.preventDefault();
        // Push the new location to React Router DOM
        history.push("/search");
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input type="search" name="q" placeholder="search"></input>
        </form>
      );
    };
    

    If you are not looking to change the implementation to use the router, I think a good solution would be to test if the action property is correct and not test native behavior, especially since it's hard to test non-router navigation changes in (example) and form submission isn't fully implemented into JSDOM.

    it('has form action for the search page', () => {
      const history = createMemoryHistory();
      const header = render(
        <Router history={history}>
          <SearchBar />
        </Router>
      );
    
      const searchBar = header.getByPlaceholderText('search');
      expect(searchBar).toHaveAttribute('action', '/search');
    });