reactjsjestjsreact-testing-libraryreact-toastify

How to test react-toastify with jest and react-testing-library


I have a screen with some form, and on submission, I send the request to back-end with axios. After successfully receiving the response, I show a toast with react-toastify. Pretty straight forward screen. However, when I try to test this behavior with an integration test using jest and react testing library, I can't seem to make the toast appear on DOM.

I have a utility renderer like that to render the component that I'm testing with toast container:

import {render} from "@testing-library/react";
import React from "react";
import {ToastContainer} from "react-toastify";

export const renderWithToastify = (component) => (
  render(
    <div>
      {component}
      <ToastContainer/>
    </div>
  )
);

In the test itself, I fill the form with react-testing-library, pressing the submit button, and waiting for the toast to show up. I'm using mock service worker to mock the response. I confirmed that the response is returned OK, but for some reason, the toast refuses to show up. My current test is as follows:

expect(await screen.findByRole("alert")).toBeInTheDocument();

I'm looking for an element with role alert. But this seems to be not working. Also, I tried doing something like this:

...

beforeAll(() => {
  jest.useFakeTimers();
}

...

it("test", () => {
  ...

  act(() =>
    jest.runAllTimers();
  )
  expect(await screen.findByRole("alert")).toBeInTheDocument();
}

I'm kind of new to JS, and the problem is probably due to asynch nature of both axios and react-toastify, but I don't know how to test this behavior. I tried a lot of things, including mocking timers and running them, mocking timers and advancing them, not mocking them and waiting etc. I even tried to mock the call to toast, but I couldn't get it working properly. Plus this seems like an implementation detail, so I don't think I should be mocking that.

I think the problem is I show the toast after the axios promise is resolved, so timers gets confused somehow.

I tried to search many places, but failed to find an answer.

Thanks in advance.


Solution

  • Thank you @Estus Flask, but the problem was much much more stupid :) I had to render ToastContainer before my component, like this:

    import {render} from "@testing-library/react";
    import React from "react";
    import {ToastContainer} from "react-toastify";
    
    export const renderWithToastify = (component) => {
      return (
        render(
          <div>
            <ToastContainer/>
            {component}
          </div>
        )
      );
    };
    

    Then, the test was very simple, I just had to await on the title of the toast:

    expect(await screen.findByText("alert text")).toBeInTheDocument();
    

    The findByRole doesn't seem to work for some reason, but I'm too tired to dig deeper :) I didn't have to use any fake timers or flush the promises. Apperently, RTL already does those when you use await and finBy* queries, only the order of rendering was wrong.