Home > database >  How to test a function that's called inside a useEffect hook
How to test a function that's called inside a useEffect hook

Time:09-09

I have a function that is called inside a useEffect and I'm not able to pass coverage to there. The function changes the value of a state depending of the viewport width, for render html. Basically I do a conditional rendering. This is the code of the function updateMedia:

import { useEffect, useState } from "react";
import { Contact } from "../../features/contacts/models/Contact";
import IndividualContactStyled from "./IndividualContactStyled";

interface ContactProps {
  contact: Contact;
}

// eslint-disable-next-line @typescript-eslint/no-redeclare
const IndividualContact = ({ contact }: ContactProps): JSX.Element => {
  const initialState = false;

  const [isDesktop, setIsDesktop] = useState(initialState);

  const updateMedia = () => {
    setIsDesktop(window.innerWidth > 799);
  };

  useEffect(() => {
    window.addEventListener("resize", updateMedia);
    return () => window.removeEventListener("resize", updateMedia);
  });

  return (
    <IndividualContactStyled className="contact">

        {isDesktop && <span className="contact__email">{contact.email}</span>}
        {isDesktop && (
          <span className="contact__phoneNumber">{contact.phoneNumber}</span>
        )}
      </div>
    </IndividualContactStyled>
  );
};

export default IndividualContact;

Now, the coverage don't pass for the updateMedia function. I've made this test, if it helps:

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

describe("Given a IndividualContact component", () => {
  describe("When it is instantiated with a contact and in a viewport bigger than 800px", () => {
    const contact = {
      name: "Dan",
      surname: "Abramov",
      email: "[email protected]",
      phoneNumber: "888555222",
      owner: "owner",
    };

    test("Then it should render the 'email' and the 'phoneNumber' of the contact", async () => {
      global.innerWidth = 1000;
      global.dispatchEvent(new Event("resize"));

      render(<IndividualContact contact={contact} />);

      await waitFor(() => {
        expect(screen.getByText("[email protected]")).toBeInTheDocument();
      });
    });
  });
});

If anyone can help me I would be very grateful. Thanks!

CodePudding user response:

You should render the component and register the resize event on the window first. Then change the value of window.innerWidth and dispatch a resize event on the window.

E.g.

index.tsx:

import React, { useEffect, useState } from 'react';

type Contact = any;
interface ContactProps {
  contact: Contact;
}

const initialState = false;
const IndividualContact = ({ contact }: ContactProps) => {
  const [isDesktop, setIsDesktop] = useState(initialState);

  const updateMedia = () => {
    setIsDesktop(window.innerWidth > 799);
  };

  useEffect(() => {
    window.addEventListener('resize', updateMedia);
    return () => window.removeEventListener('resize', updateMedia);
  });

  return (
    <div>
      {isDesktop && <span className="contact__email">{contact.email}</span>}
      {isDesktop && <span className="contact__phoneNumber">{contact.phoneNumber}</span>}
    </div>
  );
};

export default IndividualContact;

index.test.tsx:

import IndividualContact from './';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import React from 'react';

describe('Given a IndividualContact component', () => {
  describe('When it is instantiated with a contact and in a viewport bigger than 800px', () => {
    const contact = {
      name: 'Dan',
      surname: 'Abramov',
      email: '[email protected]',
      phoneNumber: '888555222',
      owner: 'owner',
    };

    test("Then it should render the 'email' and the 'phoneNumber' of the contact", async () => {
      render(<IndividualContact contact={contact} />);

      global.innerWidth = 1000;
      act(() => {
        global.dispatchEvent(new Event('resize'));
      });

      await waitFor(() => {
        expect(screen.queryByText('[email protected]')).toBeInTheDocument();
      });
    });
  });
});

Test result:

 PASS  stackoverflow/73652164/index.test.tsx (11.685 s)
  Given a IndividualContact component
    When it is instantiated with a contact and in a viewport bigger than 800px
      ✓ Then it should render the 'email' and the 'phoneNumber' of the contact (43 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 index.tsx |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.385 s

package versions:

"jest": "^26.6.3",
"@testing-library/react": "^11.2.7",
"react": "^16.14.0",
  • Related