代码之家  ›  专栏  ›  技术社区  ›  Robin

测试时在React组件回调中使用sinon-fakes

  •  1
  • Robin  · 技术社区  · 4 年前

    我遇到了一些我无法解释的事情。我有以下测试:

    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.callCount 0 而且没有理由去核实。

    奇怪的是,当我打电话给 saveUrlFake 我自己与 saveUrlFake('https://shaw:IMissYouR00t@example.com/poi') 测试成功了。我的问题是,为什么React组件没有调用我的sinon fake?

    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,
          username,
          password,
          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>
      );
    }
    
    0 回复  |  直到 4 年前
        1
  •  0
  •   Robin    4 年前

    回调似乎是异步调用的,因此需要等待。测试库提供 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');
      });
    });
    
    
    推荐文章