vendredi 10 janvier 2020

test on click call custom hook to post data using fetch

I am trying to write a test which simulates a user clicking on a button which calls my custom hook which is designed to post data. I am fairly new to React and testing with React.

I have been trying to use renderHook from the @testing-library/react-hooks with not much luck.

here is what the component I am testing looks like

export default function ConfirmatonChange(props) {
  const componentIsMounted = useRef(true);
  const [shouldPost, setShouldPost] = useState(false);
  const [data, loading, isError, errorMessage] = UsePostOrPutFetch(
    '/send-email/',
    props.data.location.state.value,
    'POST',
    shouldPost
  );
  const [countDown, setCountDown] = useState(5);

  useEffect(() => {
    return () => {
      componentIsMounted.current = false;
    };
  }, []);

  const spinner = <Spinner />;

  const changeView = () => {
    if (countDown < 0) {
      return <Redirect to='/' />;
    } else {
      setTimeout(() => {
        if (componentIsMounted.current) {
          setCountDown(countDown - 1);
        }
      }, 1000);
    }
  };

  const sendEmail = () => {
    if (componentIsMounted.current) {
      setShouldPost(true);
    } else {
      setShouldPost(false);
    }
  };

  return (
    <div>
      <div className='ConfirmationChange-content'>
        <article className='c-tile'>
          <div className='c-tile__content'>
            <div className='c-tile__body u-padding-all'>
              {props.data.location.state.value.change_type === 'planned' ? (
                <img
                  className='ConfirmationChange-Planned-Banner'
                  src={plannedBanner}
                  alt='planned-banner'
                />
              ) : (
                <img
                  className='ConfirmationChange-Emergency-Banner'
                  src={emergencyBanner}
                  alt='emergency-banner'
                />
              )}
              <div className='ConfirmationChange-content-items'>
                <h2 className='c-heading-bravo'>Confirmation</h2>
                <label className='ConfirmationChange-label'>Change Type:</label>
                <p className='c-text-body'>
                  {' '}
                  {props.data.location.state.value.change_type}
                </p>
              </div>
              <div className="ConfirmationChange-Button">
              <button
                className='c-btn c-btn--primary u-margin-right'
                onClick={props.data.history.goBack}
              >
                Edit
              </button>
              <button
                className='c-btn c-btn--secondary u-margin-right'
                disabled={loading}
                onClick={() => {
                  sendEmail();
                }}
              >
                {' '}
                Send{' '}
              </button>

              {!loading && data === 200 && !isError ? (
                <div className='ConfirmationChange-success-send'>
                  <hr hidden={!data === 200} className='c-divider' />
                  Email Sent succesfully
                  <p>You will be redirected shortly....{countDown}</p>
                  {changeView()}
                </div>
              ) : loading && !isError ? (
                spinner
              ) : (
                <div className='ConfirmationChange-error-send'>
                  <hr hidden={!isError} className='c-divider' />
                  {errorMessage}
                </div>
              )}
              </div>

            </div>
          </div>
        </article>
      </div>
    </div>
  );
}

So when the user click on the send button this calls the sendEmail() function which sets the setShouldPost to true and my custom hook is called UsePostOrPutFetch. Once there is data and it returns true a success div is shown which has then starts a count down timer that redirects the user to the home page.

Here is what my hook looks like

import { useState, useEffect, useRef } from 'react';
import { adalApiFetch } from '../config/adal-config';

const UsePostOrPutFetch = (
  url,
  sendData,
  methodType,
  shouldFetch
) => {
  const isCurrent = useRef(true);
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [errorMessage, setError] = useState("");

  useEffect(() => {
    return () => {
      isCurrent.current = false;
    };
  }, []);

  useEffect(() => {
    const ac = new AbortController();

    if (shouldFetch) {
      const postOrPutData = async () => {
        try {
          const response = await adalApiFetch(fetch, url, {
            signal: ac.signal,
            method: methodType,
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify(sendData)
          });
          const json = await response.json();
          if (isCurrent) {
            setData(json);
            setLoading(true);
          }
        } catch (err) {
          setIsError(true);
          setError(err.message);
        } finally {
          setLoading(false);
        }
      };
      postOrPutData();
    }
    return () => {
      ac.abort();
    };
  }, [shouldFetch, sendData, url, methodType]);

  return [data, loading, isError, errorMessage];
};

export { UsePostOrPutFetch };

Using adalApiFetch which looks like this

export const adalApiFetch = (fetch, url, options) =>
    adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);

Library is https://github.com/salvoravida/react-adal#readme

Here is what my test looks like

import React from 'react';
import { mount } from 'enzyme';
import { act } from "react-dom/test-utils";
import ConfirmationChange from "../components/ConfirmatonChange";
import {UserContext} from "../context/userContext";

import {renderHook} from '@testing-library/react-hooks';
import fetchMock from 'fetch-mock';

// ensure the resetting modules before each test
beforeEach(() => {
  jest.resetModules();
});

jest.mock("./../config/adal-config");

const commonProps = {
  data: {
    history: {},
    location: {
      state: {
        value: {
          change_type: "planned",
          header_text: "header text",
          change_summary: "this is a change summary",
          justification: "this is a justification",
          impact_venue_region: "all",
          impact: "some impact",
          impact_description: "some impact description",
          roll_back: "roll back",
          roll_back_duration: "1 hour",
          start_of_maintenance: "2019-04-27",
          expected_end_of_maintenance: "2020-04-27",
          mail_jet_contact_list: "tkgwljihm",
          spark_ticket_id: "",
          isSave: "someemail@email.com",
          user: {
            email: "someemail@email.com"
          }
        }
      }
    }
  }
};

const user = { email: "ergun.polat@sky.uk"};

/**
 * This is a factory method to create a shallow method for the ConfirmationChange component.
 * @function
 * @param {object} props - Component properties to this setup.
 * @param {object} state
 * @returns {mountedWrapper}
 */
// const setup = (props = {}) => {
//   return mount(<ConfirmationChange {...props} />);
// };
const setup = (props = {}) => {
  return mount(<UserContext.Provider value={user}> 
      <ConfirmationChange {...props}/>
    </UserContext.Provider>);
};

/**
 * Returns mountedWrapper containt node(s) with given attribute value.
 * @param {mountedWrapper} wrapper - Enzyme mounted wrapper to search within.
 * @param {string} val - Value of attribute for search.
 */
const findByTestAttr = (wrapper, val) => {
  return wrapper.find(`${val}`);
};

test("reneders without failing", () => {
  const wrapper = setup(commonProps);
  expect(wrapper.length).toBe(1);
});

test("displays confirmation change information with all data", () => {
  const wrapper = setup(commonProps);
  expect(wrapper.contains("planned")).toBe(true);
  expect(wrapper.contains("this is a change summary")).toBe(true);
  expect(wrapper.contains("header text")).toBe(true);
  expect(wrapper.contains("this is a justification")).toBe(true);
  expect(wrapper.contains("some impact")).toBe(true);
  expect(wrapper.contains("some impact description")).toBe(true);
  expect(wrapper.contains("roll back")).toBe(true);
  expect(wrapper.contains("1 hour")).toBe(true);
  expect(wrapper.contains("27/04/2019 00:00")).toBe(true);
  expect(wrapper.contains("27/04/2020 00:00")).toBe(true);
});


test("when send button clicked email is sent successfully", async () => {
  beforeEach(() => {
    global.fetch = fetch;
  });
  afterEach(() => {
    fetchMock.restore();
  });
  fetchMock.mock('*', {
        data: 200
    });

  jest.setTimeout(30000);

  const {
    data,
    waitForNextUpdate
  } = renderHook(() => UsePostOrPutFetch("/send-email/",commonProps.data.location.state.value,"POST",true));

  await act(async ()=> {

    const wrapper = setup(commonProps);
    const sendButton = findByTestAttr(wrapper, '.c-btn--secondary');
    expect(sendButton.text()).toBe(' Send ');
    sendButton.simulate('click');
    await waitForNextUpdate();

     wrapper.update();
     console.log(wrapper.debug());
  });
});

Any help would be greatly appreciated.

Aucun commentaire:

Enregistrer un commentaire