测试时在 React 组件回调上使用 sinon fakes tl;博士

问题描述

我遇到了无法解释的事情。我进行了以下测试:

import React from 'react';
import { render,screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
import * as sinon from 'sinon';
import TestApp from '../../views/App.test';
import ReplicationForm from './ReplicationForm';

describe('ReplicationForm component',() => {
  it('calls saveUrl on submit with new url',() => {
    const saveUrlFake = sinon.fake();
    const otherFake = (url: string) => console.log(url);

    render(<ReplicationForm saveUrl={otherFake} />,{ wrapper: TestApp });
    (screen.getByLabelText('Hostname') as HTMLInputElement).value = 'https://example.com';
    (screen.getByLabelText('Database') as HTMLInputElement).value = 'poi';
    (screen.getByLabelText('Username') as HTMLInputElement).value = 'shaw';
    (screen.getByLabelText('Password') as HTMLInputElement).value = 'IMissYouR00t';
    userEvent.click(screen.getByRole('button',{ name: 'Submit' }));

    console.log(saveUrlFake.callCount);
    expect(saveUrlFake.lastCall.firstArg).to.equal('https://shaw:IMissYouR00t@example.com/poi');
  });
});

当我使用 otherFake 时,回调被正确调用并且所需的输出被打印到控制台。不幸的是,如果我使用 saveUrlFake,它永远不会被调用saveUrlFake.callCount0 并且没有要检查的参数。

奇怪的是,当我自己用 saveUrlFake 调用 saveUrlFake('https://shaw:IMissYouR00t@example.com/poi') 时,测试有效。我的问题是,为什么我的 sinon fake 没有被 React 组件调用

tl;博士

有问题的完整组件:

import styled from '@emotion/styled';
import { Button,TextField,withTheme } from '@material-ui/core';
import React from 'react';
import { useForm } from 'react-hook-form';
import { FormattedMessage,useIntl } from 'react-intl';

const StyledForm = withTheme(
  styled.form`
    display: flex;
    flex-direction: column;

    & > *:not(div:first-of-type) {
      margin-top: ${({ theme }) => theme.spacing(2)}px;
    }
  `
);

type FormModel = {
  origin: string;
  database: string;
  username: string;
  password: string;
};

type ReplicationFormProps = {
  url?: string;
  saveUrl: (url: string) => void;
};

export default function ReplicationForm({ url,saveUrl }: ReplicationFormProps) {
  const intl = useIntl();
  const getDefaultValuesFromURL = (inputUrl: string): FormModel => {
    const { origin,username,password,pathname } = new URL(inputUrl);
    return {
      origin,database: pathname.replaceAll('/','')
    };
  };
  const defaultValues: FormModel = url
    ? getDefaultValuesFromURL(url)
    : {
        origin: '',database: '',username: '',password: ''
      };
  const { handleSubmit,formState,register } = useForm<FormModel>({ defaultValues });

  const onSubmit = (data: FormModel) => {
    const newUrl = new URL(data.origin);
    newUrl.username = data.username;
    newUrl.password = data.password;
    newUrl.pathname = `/${data.database}`;

    saveUrl(newUrl.toString());
  };

  return (
    <StyledForm onSubmit={handleSubmit(onSubmit)}>
      <TextField
        id="replication-form-origin"
        name="origin"
        inputRef={register}
        label={intl.formatMessage({ id: 'label.hostname',defaultMessage: 'Hostname' })}
        fullWidth
      />
      <TextField
        id="replication-form-database"
        name="database"
        inputRef={register}
        label={intl.formatMessage({ id: 'label.database',defaultMessage: 'Database' })}
        fullWidth
      />
      <TextField
        id="replication-form-username"
        name="username"
        inputRef={register}
        label={intl.formatMessage({ id: 'label.username',defaultMessage: 'Username' })}
        fullWidth
      />
      <TextField
        id="replication-form-password"
        name="password"
        type="password"
        inputRef={register}
        label={intl.formatMessage({ id: 'label.password',defaultMessage: 'Password' })}
        fullWidth
      />

      <Button type="submit" color="primary" disabled={formState.isDirty}>
        <FormattedMessage id="action.submit" defaultMessage="Submit" />
      </Button>
    </StyledForm>
  );
}

解决方法

看来回调是异步调用的,所以需要等待。测试库提供了 waitFor(),可在此类情况下提供帮助。

import React from 'react';
import { render,screen,waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
import * as sinon from 'sinon';
import TestApp from '../../views/App.test';
import ReplicationForm from './ReplicationForm';

describe('ReplicationForm component',() => {
  it('calls saveUrl on submit with new url',async () => {
    const saveUrlFake = sinon.fake();

    render(<ReplicationForm saveUrl={saveUrlFake} />,{ wrapper: TestApp });
    userEvent.type(screen.getByLabelText('Hostname *') as HTMLInputElement,'https://example.com');
    userEvent.type(screen.getByLabelText('Database *'),'poi');
    userEvent.type(screen.getByLabelText('Username *'),'shaw');
    userEvent.type(screen.getByLabelText('Password *'),'IMissYouR00t');
    userEvent.click(screen.getByRole('button',{ name: 'Submit' }));

    await waitFor(() => expect(saveUrlFake.called).to.be.true);
    expect(saveUrlFake.lastCall.firstArg).to.equal('https://shaw:IMissYouR00t@example.com/poi');
  });
});

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...