3

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)

2 Answers2

3

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.

Richard Hpa
  • 2,353
  • 14
  • 23
  • Thanks. I have tried that and due to the fact that both `waitFor` and `findByAltText` are async, I get this message by Jest: _Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one._ - and the test will fail. – user3828282 Jul 19 '21 at 08:10
  • I often get that when I have 2 async calls happening at the same time, which is probably the waitFor and findByAltText conflicting. Maybe remove the await for and just have the findByAltText. – Richard Hpa Jul 19 '21 at 20:28
  • The problem is that you are not waiting for anything. The waitFor callback is not awaiting the findBy call, which is a promise. With this, the waitFor will be fulfilled and your findBy will probably overlap with the getBy call, you see? As Richard Hpa said, awaiting just for the findBy might be enough, the waitFor is unnecessary. In fact, the [findBy is a combination of getBy + waitFor](https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries) ;) – Michel Sabchuk Nov 16 '21 at 10:51
0

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

   if (fxPair !== undefined) {
  • Thank you. Do you know a clear guide on how to mock redux in tests, please? What I have found online is inconsistent and confusing. – user3828282 Jul 20 '21 at 21:14
  • It's tricky, can pass store state to ProviderWrapper, or can just mock redux hooks https://stackoverflow.com/questions/56827300/how-to-test-a-component-using-react-redux-hooks https://medium.com/@szpytfire/react-redux-testing-mocking-useselector-and-usedispatch-e004c3f2b2e0 – Aleksandr Smyshliaev Jul 20 '21 at 21:23