user3828282
user3828282

Reputation: 31

react-testing-library: Test evaluation happens too early

I want to test a React functional component for a currency exchange list item. This component should show also a flag image and a currency code. The UI gets the currency code from the component's props (currencyCode) and the image alt text from a state slice (imgAlt).

See the UI here

Component:

import React from "react";
import { useSelector } from "react-redux";
import classes from "./FXPair.module.css";

const FXPair = ({ currencyCode }) => {
  const fxPair = Object.values(useSelector((state) => state.fxPairs)).filter((pair) => pair.currency === currencyCode)[0];
  const [fXRate, setFXRate] = React.useState(1.0);
  const [flagImgSrc, setFlagImgSrc] = React.useState("");
  const [imgAlt, setImgAlt] = React.useState("");

  React.useEffect(() => {
    if (fxPair !== undefined) {
      (async function fetchImg() {
        await import(`../../flags/${fxPair.currency.slice(0, 2).toLowerCase()}.png`).then((image) => {
          setImgAlt(`${fxPair.currency.slice(0, 2).toLowerCase()}-flag`);
          setFlagImgSrc(image.default);
        });
      })();
      setFXRate(fxPair.exchangeRate.middle);
    }
  }, [fxPair, flagImgSrc, imgAlt, fXRate]);

  return (
    <div className={classes.FXPair}>
      <img src={flagImgSrc} alt={imgAlt} />
      <p className={classes.CurrencyToBuy}>{currencyCode}</p>
      <p className={classes.fXRate}>EUR {(1 / fXRate).toFixed(3)}</p>
    </div>
  );
};

export default FXPair;

When testing the component by the test below, I want to make sure the correct flag-image alt text and the currency code get displayed. The test evaluation, however, does not work. I have tried using a different approach in each test, but none of them does the job.

Tests:

import { render, screen, waitFor } from "@testing-library/react";
import ProviderWrapper from "../../testUtils/ProviderWrapper";
import FXPair from "./FXPair";

test("Flag gets displayed for the currency-related country", async () => {
  render(
    <ProviderWrapper>
      <FXPair currency={"HUF"} />
    </ProviderWrapper>
  );

  await waitFor(() => {
    screen.getByAltText("hu-flag");
  });
  expect(screen.getByAltText("hu-flag")).toBeInTheDocument();
});

test("Currency code gets displayed", async () => {
  const currencyCode = "HUF";
  render(
    <ProviderWrapper>
      <FXPair currency={currencyCode} />
    </ProviderWrapper>
  );

  const items = await screen.findByText(/HUF/);
  expect(items.getByText(/HUF/)).toBeInTheDocument();
});

Test results: You can see that the value "HUF" (Hungarian Forint) which I pass in for the prop currencyCode is not taken into consideration. Also the tests do not wait for the state slice imgAlt, rather the tests evaluate based on the initial value of this slice.

  ● Flag gets displayed for the currency-related country

    Unable to find an element with the alt text: hu-flag

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <div
          class="FXPair"
        >
          <img
            alt=""
            src=""
          />
          <p
            class="CurrencyToBuy"
          />
          <p
            class="fXRate"
          >
            EUR 
            1.000
          </p>
        </div>
      </div>
    </body>

      10 |   );
      11 |
    > 12 |   await waitFor(() => {
         |         ^
      13 |     screen.getByAltText("hu-flag");
      14 |   });
      15 |   expect(screen.getByAltText("hu-flag")).toBeInTheDocument();

      at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:175:27)
      at Object.<anonymous> (src/components/FXPair/FXPair.test.jsx:12:9)

  ● Currency code gets displayed

    Unable to find an element with the text: /HUF/. 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.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <div
          class="FXPair"
        >
          <img
            alt=""
            src=""
          />
          <p
            class="CurrencyToBuy"
          />
          <p
            class="fXRate"
          >
            EUR 
            1.000
          </p>
        </div>
      </div>
    </body>

      24 |   );
      25 |
    > 26 |   const items = await screen.findByText(/HUF/);
         |                              ^
      27 |   expect(items.getByText(/HUF/)).toBeInTheDocument();
      28 | });
      29 |

      at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:175:27)
      at findByText (node_modules/@testing-library/dom/dist/query-helpers.js:101:33)
      at Object.<anonymous> (src/components/FXPair/FXPair.test.jsx:26:30)

Upvotes: 3

Views: 1903

Answers (2)

Aleksandr Smyshliaev
Aleksandr Smyshliaev

Reputation: 420

You get fxPair from redux, do you mock redux in tests? Try to put console.log in component and debug.

   if (fxPair !== undefined) {

Upvotes: 0

Richard Hpa
Richard Hpa

Reputation: 2867

try using findBy rather than getBy for your first check. This will return a promise which waits 1000ms(by default) to find the element, and if it still cant find it then it will fail.

  await waitFor(() => {
    screen.findByAltText("hu-flag");
  });
  expect(screen.getByAltText("hu-flag")).toBeInTheDocument();

With my tests I often use findBy first and the getBy in all of them after that.

Upvotes: 3

Related Questions