Jest.js 在拍摄快照之前等待Material-UI涟漪完成的最佳方法

evrscar2  于 2023-05-21  发布在  Jest
关注(0)|答案(2)|浏览(179)

我希望从React测试库社区获得一些Maven建议和指导,以确保在拍摄快照之前完成Material UI涟漪动画的最佳方法。
这个问题一直导致我们进行flakey测试,所有内容都在本地通过,但当我们在CI服务器上运行时,测试是间歇性的,并且由于动画完成的速度比本地快,这意味着它与存储的快照不匹配。
只有在我们将fireEvent替换为userEvent时才会出现这个问题,这对userEvent提供的附加操作是有意义的,但我想得到的是存储不包含任何UI转换的快照。
下面我创建了一个简单的测试来帮助说明这个问题(也是在codesandbox上):

import React from "react";
    import { render, screen, fireEvent, waitFor } from "@testing-library/react";
    import userEvent from "@testing-library/user-event";
    import Button from "@material-ui/core/Button";
    
    test("clicks material button with fireEvent - no ripple implication", () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      fireEvent.click(btnContainer);
      screen.debug(btnContainer);
    });
    
    test("clicks material button with userEvent", async () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      userEvent.click(btnContainer);
      screen.debug(btnContainer);
    });
    
    test("clicks material button with userEvent wait for ripples", async () => {
      render(
        <Button variant="contained" color="primary">
          Ripple
        </Button>
      );
    
      const btnContainer = screen.getByRole("button", { name: /ripple/i });
    
      expect(btnContainer).toBeInTheDocument();
      screen.debug(btnContainer);
    
      userEvent.click(btnContainer);
      screen.debug(btnContainer);
    
      await waitForRippleToRemove(btnContainer);
      // without the ripple transition being complete inconsitent tests runs can occur
      // when using snapshots as per commented line below
      // (ie some have ripples some don't)
      // expect(asFragment()).toMatchSnapshot();
    
      screen.debug(btnContainer);
    });
    
    function waitForRippleToRemove(container) {
      return waitFor(() => {
        expect(
          container.querySelector("span.MuiTouchRipple-root")
        ).toBeEmptyDOMElement();
      });
    }

第一个测试使用fireEvent运行良好,按钮总是看起来像:

<button
      class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
      tabindex="0"
      type="button"
    >
      <span
        class="MuiButton-label"
      >
        Ripple
      </span>
      <span
        class="MuiTouchRipple-root"
      />
    </button>

但是,当在测试2中使用userEvent时,单击后按钮看起来如下所示:

<button
      class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary"
      tabindex="0"
      type="button"
    >
      <span
        class="MuiButton-label"
      >
        Ripple
      </span>
      <span
        class="MuiTouchRipple-root"
      >
        <span
          class="MuiTouchRipple-ripple MuiTouchRipple-rippleVisible"
          style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
        >
          <span
            class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
          />
        </span>
      </span>
    </button>

span.MuiTouchRipple-ripple元素将作为过渡的一部分被删除,但我的问题是什么是最好的方法来等待这种情况发生,因为我的测试显示我正在检查实现细节(即使用第三方依赖的类名),这感觉有点可怕。

await waitFor(() => {
        expect(
            container.querySelector("span.MuiTouchRipple-root")
        ).toBeEmptyDOMElement();
    });

此外,值得指出的是,根据测试中的组件,我们可能不得不多次使用userEvent.click,因此在整个测试过程中多次调用waitForRippleToRemove(btnContainer)似乎是错误的,但目前是一个可行的解决方案。

bkhjykvo

bkhjykvo1#

正如您已经发现的,这里的问题是单击按钮时的涟漪动画。
我没有看过MaterialUI代码,但是动画通常是用某种计时器实现的,比如setTimeout
您通常可以通过使用jest.useFakeTimers();来模拟它们,如下所示。

beforeEach(() => jest.useFakeTimers());

test('matches snapshot after clicking button', () => {
  const { asFragment } = render(<Button>Ripple</Button>);

  userEvent.click(screen.getByRole('button', { name: 'Ripple' }));
  jest.runAllTimers();

  expect(asFragment()).toMatchSnapshot();
});

注意事项

在一些测试中使用fireEvent.click似乎是一个公平的妥协。特别是如果你已经在不同的测试中在同一个按钮上触发了userEvent.click

来源

ss2ws0br

ss2ws0br2#

当应用在test-runner中运行时,我全局禁用了涟漪:

const muiTheme = createTheme({
    components: {
        MuiButtonBase: {
            defaultProps: {
                // Disable ripple for jest tests
                disableRipple: !!process.env.JEST_WORKER_ID,
            },
        },
     }
});

相关问题