Error when submit the form using jest and testing-library

27 Views Asked by At

I have this component, that is a form for a client regitrastion, it works well on the browser, but when I run my tests, I get an error:

import * as country from "@/entities/country"
import { Button, Input, Form, message, Select, DatePicker, AutoComplete, Upload } from 'antd';
import { toast } from 'react-toastify';
import { useNavigate } from 'react-router-dom';
import { registerCustomerFx, uploadCustomerDocumetFx } from '@/entities/customer';
import { useUnit } from 'effector-react';
import { useEffect, useState } from "react";
import { removeAccents } from "@/shared/utils/word";
import dayjs from "dayjs";
import { RcFile, UploadProps } from "antd/es/upload";
import { FileType, getBase64 } from "@/shared/lib/image";
import { UploadOutlined } from '@ant-design/icons';
import { UploadButton } from "@/shared/ui/UploadButton";
import { africanCountryCodes } from "@/shared/data/ddd";
import { documentTypes } from "@/shared/data/documents";

export const ClientRegistrationForm = () => {
  const [ form ] = Form.useForm();
  const navigate = useNavigate();
  const countriesList = useUnit(country.$getCountriesState);
  const [ imageUrl, setImageUrl ] = useState<string>();
  const [ loading, setLoading ] = useState(false);
  const [ documentType, setDocumentType ] = useState("");

  const onFinish = async (values: any) => {
    try {
      const { prefix, phoneNumber, documentType, clientDocuments, ...registValues } = values;      
      
      const response = await registerCustomerFx({
        ...registValues,
        phoneNumber: `${ prefix }${ phoneNumber }`,
        documents: [{ 
          title: documentType, 
          type: documentType, 
          urls: clientDocuments.map((item: any) => item.response.url)
        }]
      });

      message.success('Documentos submetidos com sucesso.');
      navigate(`/clientes/${ response.data.customer._id }/registrar-apolice`);      
    } catch (error: any) {
      toast.error(error.response.data.message);
    }
  };

  const handleUpload = async ({ file, onSuccess, name }: { file: string | Blob | RcFile; onSuccess: Function | undefined; name: string }) => {
    try {
      const formData = new FormData();
      formData.append('file', file);

      const response = await uploadCustomerDocumetFx(formData);

      const data = response.data;

      const { url } = data;

      const fieldValue = form.getFieldValue(name);
      const updatedFieldValue = [...(fieldValue || []), url];
      form.setFieldsValue({ [name]: updatedFieldValue });

      if (onSuccess) {
        onSuccess(data, file);
      }

      message.success("Ficheiro carregado!");
    } catch (error) {
      message.error("Ocorreu algum erro!");
    }
  };

  const handleChange: UploadProps['onChange'] = (info) => {
    if (info.file.status === 'uploading') {
      setLoading(true);
      return;
    }
    if (info.file.status === 'done') {
      getBase64(info.file.originFileObj as FileType, (url) => {
        setLoading(false);
        setImageUrl(url);
      });
    }
  };

  useEffect(() => {
    country.getCountriesFx();
  }, []);

  return (
    <Form 
      form={form} 
      layout="vertical" 
      onFinish={onFinish}
      initialValues={{
        prefix: "+258"
      }}
      data-testid="client-registration-form"
    >
      <h2 className="text-2xl font-semibold mb-5">Registrar novo cliente</h2>

      <div className="grid grid-cols-1 md:grid-cols-2 w-full gap-2">
        <Form.Item
          name="firstName"
          label="Primeiro nome"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, coloque o primeiro nome do cliente!' }]}
        >
          <Input placeholder="John" className="py-3" />
        </Form.Item>

        <Form.Item
          name="lastName"
          label="Último nome"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, coloque o último nome do cliente!' }]}
        >
          <Input placeholder="Doe" className="py-3" />
        </Form.Item>

        <Form.Item
          name="nuit"
          label="NUIT"
          className="w-full md:max-w-sm"
          rules={[{ required: false, message: 'Por favor, coloque o nuit!' }]}
        >
          <Input placeholder="767867856" className="py-3" />
        </Form.Item>

        <Form.Item
          name="identityCard"
          label="Número de BI"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, coloque o número de documento!' }]}
        >
          <Input placeholder="6786786667M" className="py-3" />
        </Form.Item>

        <Form.Item
          name="country"
          label="Nacionalidade"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, selecione um país!' }]}
        >
          <AutoComplete
            size="large"
            placeholder="Selecione o país"
            options={ countriesList }
            filterOption={(inputValue, option) =>
              removeAccents(option?.label).toUpperCase().indexOf(removeAccents(inputValue.toUpperCase())) !== -1
            }
          />
        </Form.Item>

        <Form.Item
          name="phoneNumber"
          label="Número de celular"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, coloque o número de celular!' }]}
        >
          <Input 
            addonBefore={
              <Form.Item name="prefix" noStyle>
                <AutoComplete
                  size="large"
                  options={ africanCountryCodes }
                  style={{ width: 100 }}
                  filterOption={(inputValue, option) =>
                    option?.label.indexOf(inputValue.toUpperCase()) !== -1
                  }
                />
              </Form.Item>
            }
            placeholder="841234567" 
          />
        </Form.Item>

        <Form.Item
          name="genre"
          label="Genero"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, selecione o genero!' }]}
        >
          <Select size="large" data-testid="genre" placeholder="Selecione o genero">
            <Select.Option data-testid="genre-male" value="masculino">Masculino</Select.Option>
            <Select.Option data-testid="genre-female" value="femenino">Femenino</Select.Option>
          </Select>
        </Form.Item>

        <Form.Item
          label="Data de Nascimento"
          name="birthDate"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, insira a data de nascimento' }]}
        >
          <DatePicker 
            format="DD/MM/YYYY"
            maxDate={dayjs(new Date()).subtract(18, "year")}
            className="relative flex gap-3 w-full p-2 py-3 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
          />
        </Form.Item>

        <Form.Item
          name="address"
          label="Endereço"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, coloque o endereço!' }]}
        >
          <Input placeholder="Mavalane A" className=" py-3" />
        </Form.Item>

        <Form.Item
          name="documentType"
          label="Tipo de documento"
          className="w-full md:max-w-sm"
          rules={[{ required: true, message: 'Por favor, selecione o tipo de documento!' }]}
        >
          <Select 
            size="large" 
            placeholder="Selecione o genero"
            onChange={ (value) => {
              setDocumentType(value);
            } }
          >
            {
              documentTypes.map((item) => {
                return (
                  <Select.Option key={item.value} value={item.value}>{ item.label }</Select.Option>
                )
              })
            }
          </Select>
        </Form.Item>

        <Form.Item
          name="clientDocuments"
          label="Carregue o documento"
          valuePropName="fileList"
          getValueFromEvent={ e => e && e.fileList }
          rules={[{ required: true, message: 'Por favor, coloque os documentos do cliente!' }]}
        >
          {
            documentType === "NUIT" ? (
              <Upload 
                customRequest={({ file, onSuccess }) => handleUpload({ file, onSuccess, name: 'clientDocuments' })}
                accept="image/*"
                listType="picture-card"
                showUploadList={ false }
                capture={"environment"}
                className="py-3"
                onChange={ handleChange }
              >
                { imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : <UploadButton loading={ loading } /> }
              </Upload>
            ) : (
              <Upload 
                multiple
                maxCount={2}
                customRequest={({ file, onSuccess }) => handleUpload({ file, onSuccess, name: 'clientDocuments' })}
                accept="image/*"
                listType="picture"
                capture={"environment"}
                className="py-3"
              >
                <Button icon={<UploadOutlined />}>Carregar (frente e verso)</Button>
              </Upload>
            )
          }
        </Form.Item>
      </div>

      <Form.Item>
        <Button type="primary" htmlType="submit" className='group relative w-full md:max-w-52 flex py-3 px-4 justify-center border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 h-full'>
          Submeter
        </Button>
      </Form.Item>
    </Form>
  )
};

And a test written using testing-library and jest that is in the same direcory:

import { fireEvent, render, waitFor } from "@/shared/utils/test-utils";
import { ClientRegistrationForm } from ".";
import * as country from "@/entities/country";
import { registerCustomerFx } from "@/entities/customer";
import dayjs from "dayjs";
import selectEvent from 'react-select-event';

jest.mock("@/entities/customer", () => ({
  registerCustomerFx: jest.fn().mockResolvedValueOnce({
    data: { customer: { _id: "1" } }
  }),
  uploadCustomerDocumetFx: jest.fn().mockResolvedValueOnce({
    data: {
      url: "https://example.com/file.txt",
    }
  }),
}));

jest.mock("@/entities/country", () => {
  const { $getCountriesState } = jest.requireActual("@/entities/country");

  return {
    getCountriesFx: jest.fn(),
    $getCountriesState: {
      ...$getCountriesState,
      getState: jest.fn()
    }
  }
});

describe("Client registration feature", () => {
  beforeAll(() => {
    jest.spyOn(country.$getCountriesState, 'getState').mockReturnValue([
      { label: 'Mozambique', value: 'MZ' },
      { label: 'South Africa', value: 'ZA' },
    ]);    
  });

  it("should render client registration", () => {
    const screen = render(<ClientRegistrationForm />);

    // Just a test
  });

  it("should call registerCustomerFx with correct values on form submit", async () => {
    const mockRegisterCustomer = registerCustomerFx as unknown as jest.Mock;

    const todaysDate = dayjs().format("DD/MM/YYYY");

    const screen = render(<ClientRegistrationForm />);

    fireEvent.change(screen.getByLabelText(/primeiro nome/i), { target: { value: "Jeffer" } });
    fireEvent.change(screen.getByLabelText(/último nome/i), { target: { value: "Sunde" } });
    fireEvent.change(screen.getByLabelText(/nuit/i), { target: { value: "123456789" } });
    fireEvent.change(screen.getByLabelText(/número de bi/i), { target: { value: "1234567890M" } });
    fireEvent.change(screen.getByLabelText(/nacionalidade/i), { target: { value: "MZ" } });
    fireEvent.change(screen.getByLabelText(/número de celular/i), { target: { value: "843997730" } });
    selectEvent.select(screen.getByLabelText(/genero/i), "masculino");
    fireEvent.change(screen.getByLabelText(/data de nascimento/i), { target: { value: todaysDate } });
    fireEvent.change(screen.getByLabelText(/endereço/i), { target: { value: "Mavalane" } });
    selectEvent.select(screen.getByLabelText(/tipo de documento/i), "NUIT");

    const file = new File(['file contents'], 'filename.txt', { type: 'text/plain' });
    fireEvent.change(screen.getByLabelText(/carregue o documento/i), { target: { files: [file] } });

    fireEvent.submit(screen.getByTestId("client-registration-form"));

    await waitFor(() => {      
      expect(mockRegisterCustomer).toHaveBeenCalledWith({
        firstName: "Jeffer",
        lastName: "Sunde",
        nuit: "123456789",
        identityCard: "1234567890M",
        country: "MZ",
        phoneNumber: "+258843997730",
        genre: "masculino",
        birthDate: todaysDate,
        address: "Mavalane",
        documents: [{
          title: "BI",
          type: "BI",
          urls: ["https://example.com/file.txt"]
        }]
      });
    });
  });
});

But when I run my tests, I get the following error:

jeffermarcelino@Jeffers-MacBook-Pro imperial-seguros % npx jest src/features/client-registration 
 FAIL  src/features/client-registration/client-registration.test.tsx (13.417 s)
  Client registration feature
    ✓ should render client registration (707 ms)
    ✕ should call registerCustomerFx with correct values on form submit (2770 ms)

  ● Client registration feature › should call registerCustomerFx with correct values on form submit

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: {"address": "Mavalane", "birthDate": "19/03/2024", "country": "MZ", "documents": [{"title": "BI", "type": "BI", "urls": ["https://example.com/file.txt"]}], "firstName": "Jeffer", "genre": "masculino", "identityCard": "1234567890M", "lastName": "Sunde", "nuit": "123456789", "phoneNumber": "+258843997730"}

    Number of calls: 0

    Ignored nodes: comments, script, style
    <html>
      <head />
      <body>
        <div>
          <form
            class="ant-form ant-form-vertical css-dev-only-do-not-override-1xg9z9n"
            data-testid="client-registration-form"
          >
            <h2
              class="text-2xl font-semibold mb-5"
            >
              Registrar novo cliente
            </h2>
            <div
              class="grid grid-cols-1 md:grid-cols-2 w-full gap-2"
            >
              <div
                class="ant-form-item w-full md:max-w-sm css-dev-only-do-not-override-1xg9z9n ant-form-item-has-success"
              >
                <div
                  class="ant-row ant-form-item-row css-dev-only-do-not-override-1xg9z9n"
                >
                  <div
                    class="ant-col ant-form-item-label css-dev-only-do-not-override-1xg9z9n"
                  >
                    <label
                      class="ant-form-item-required"
                      for="firstName"
                      title="Primeiro nome"
                    >
                      Primeiro nome
                    </label>
                  </div>
                  <div
                    class="ant-col ant-form-item-control css-dev-only-do-not-override-1xg9z9n"
                  >
                    <div
                      class="ant-form-item-control-input"
                    >
                      <div
                        class="ant-form-item-control-input-content"
                      >
                        <input
                          aria-required="true"
                          class="ant-input css-dev-only-do-not-override-1xg9z9n ant-input-outlined ant-input-status-success py-3"
                          id="firstName"
                          placeholder="John"
                          type="text"
                          value="Jeffer"
                        />
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div
                class="ant-form-item w-full md:max-w-sm css-dev-only-do-not-override-1xg9z9n ant-form-item-has-success"
              >
                <div
                  class="ant-row ant-form-item-row css-dev-only-do-not-override-1xg9z9n"
                >
                  <div
                    class="ant-col ant-form-item-label css-dev-only-do-not-override-1xg9z9n"
                  >
                    <label
                      class="ant-form-item-required"
                      for="lastName"
                      title="Último nome"
                    >
                      Último nome
                    </label>
                  </div>
                  <div
                    class="ant-col ant-form-item-control css-dev-only-do-not-override-1xg9z9n"
                  >
                    <div
                      class="ant-form-item-control-input"
                    >
                      <div
                        class="ant-form-item-control-input-content"
                      >
                        <input
                          aria-required="true"
                          class="ant-input css-dev-only-do-not-override-1xg9z9n ant-input-outlined ant-input-status-success py-3"
                          id="lastName"
                          placeholder="Doe"
                          type="text"
                          value="Sunde"
                        />
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div
                class="ant-form-item w-full md:max-w-sm css-dev-only-do-not-override-1xg9z9n ant-form-item-has-success"
              >
                <div
                  class="ant-row ant-form-item-row css-dev-only-do-not-override-1xg9z9n"
                >
                  <div
                    class="ant-col ant-form-item-label css-dev-only-do-not-override-1xg9z9n"
                  >
                    <label
                      class=""
                      for="nuit"
                      title="NUIT"
                    >
                      NUIT
                    </label>
                  </div>
                  <div
                    class="ant-col ant-form-item-control css-dev-only-do-not-override-1xg9z9n"
                  >
                    <div
                      class="ant-form-item-control-input"
                    >
                      <div
                        class="ant-form-item-control-input-content"
                      >
                        <input
                          class="ant-input css-dev-only-do-not-override-1xg9z9n ant-input-outlined ant-input-status-success py-3"
                          id="nuit"
                          placeholder="767867856"
                          type="text"
                          value="123456789"
                        />
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div
                class="ant-form-item w-full md:max-w-sm css-dev-only-do-not-override-1xg9z9n ant-form-item-has-success"
              >
                <div
                  class="ant-row ant-form-item-row css-dev-only-do-not-override-1xg9z9n"
                >
                  <div
                    class="ant-col ant-form-item-label css-dev-only-do-not-override-1xg9z9n"
                  >
                    <label
                      class="ant-form-item-required"
                      for="i...

      77 |
      78 |     await waitFor(() => {      
    > 79 |       expect(mockRegisterCustomer).toHaveBeenCalledWith({
         |                                    ^
      80 |         firstName: "Jeffer",
      81 |         lastName: "Sunde",
      82 |         nuit: "123456789",

      at src/features/client-registration/client-registration.test.tsx:79:36
      at runWithExpensiveErrorDiagnosticsDisabled (node_modules/@testing-library/dom/dist/config.js:47:12)
      at checkCallback (node_modules/@testing-library/dom/dist/wait-for.js:124:77)
      at checkRealTimersCallback (node_modules/@testing-library/dom/dist/wait-for.js:118:16)
      at Timeout.task [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        14.728 s, estimated 55 s
Ran all test suites matching /src\/features\/client-registration/i.

My @/shared/utils/test-utils has the following things:

import { ReactElement } from 'react';
import { render as rtlRender, RenderOptions } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation((query) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

type CustomRenderOptions = Omit<RenderOptions, 'queries'>;

const customRender = (ui: ReactElement, options?: CustomRenderOptions) =>
  rtlRender(ui, { wrapper: BrowserRouter, ...options });

export * from '@testing-library/react';

export { customRender as render };

Can you guys help me solving this?

0

There are 0 best solutions below