无法弄清楚为什么jest测试在单击fireEvent后给我旧的状态

yshpjwxd  于 2023-09-28  发布在  Jest
关注(0)|答案(1)|浏览(81)

我使用Jest和@testing-library/react,同时使用来自@apollo/client/testing的MockedProvider模拟数据。在试图弄清楚如何更改单击时发生的变化时,我在这个测试中发现了这个非常奇怪的行为:

test('Test 1', async () => {
    mockAllIsIntersecting(true);
    renderTest(() => (
      <MockedApolloProvider
        mocks={[
          getProductMock({ code: '7340266_F671' }),
          appSettingsSalesDocumentMock(),
        ]}
      >
        <ProductCard
          product={productMock}
          language="no"
          translated={translatedMock}
        />
      </MockedApolloProvider>
    ));

    fireEvent.click(await screen.findByTitle('A'));
    const img = screen.getByAltText(
      'B',
    );
    console.log(img);
    expect(img.getAttribute('src')).toEqual(
      'C',
    );
  });

1.首先,单击事件假定更改图像(到图像2)
1.通过搜索“A”找到更改图像的链接(带有preventDefault的a-href标记)
1.然后我在上面做一个fireEvent.click
1.它应该从MockedApolloProvider获取模拟数据
1.图像1现在应该已经改变了源代码,但是当我试图在控制台中找到图像2时,图像仍然是旧的图像1。
但奇怪的是,如果我改变getByAltText('B'),其中B是DOM中没有的其他东西,测试打印出它不匹配,以及可用的DOM看起来像什么,现在突然变成了正确的Image(图2)。
它看起来像当screen.getByAltText('B')被成功地找到,点击事件得到恢复???
下面是正在使用的组件的Veery简化版本,试图包括重要/相关的部分:

export const ProductCard: React.FC<ProductCardProps> = memo(
  ({ product }) => {
    const [productState, setProductState] = useState(product);
    const currProduct = productState;
    const { code, url, siblings } = currProduct;

    const loadProduct = (productCode: string) => {
      getProduct({
        variables: { variantCode: productCode },
        onCompleted: (data) => {
          data.getProduct && setProductState(data.getProduct);
        },
      });
    };
    const onSiblingClick = (
      productCode: string,
      event: React.MouseEvent<HTMLElement>,
    ) => {
      event.preventDefault();
      if (currProduct.code === productCode) {
        return false;
      }
      loadProduct(productCode);
    };

    return (
      <ProductCardContainer>
        <ProductCardImageContainer>
          <ProductCardHoverElements
            currProduct={currProduct}
            product={product}
          />
        </ProductCardImageContainer>
        {/* onSiblingClick gets triggered in the test*/}
        <ProductColors
          code={code}
          productUrl={url}
          siblings={siblings}
          onSiblingClick={onSiblingClick}
          numberOfVisibleSwatches={3}
          size="small"
          ariaLabelMoreColors="More colors"
        />
      </ProductCardContainer>
    );
  },
  (prevProps, nextProps) => {
    return prevProps.product.code === nextProps.product.code;
  },
);

注意:图像1和图像2都有相同的“B”替换文本
注2:screen.findByTitle('A')是一个在组件中带有clickHandler的超链接(a-href)

7cjasjjr

7cjasjjr1#

不应该在act中 Package fireEvent的原因是它是多余的,因为fireEvent已经 Package 在act中了。然而,这里有一个重要的区别。你不仅仅是在act中 Package fireEvent,而是在异步行为中 Package 它并等待它:

await act(async() => {
  await fireEvent.click('somthing');
})

这与fireEvent Package 在其中的正常行为不同,不应该从esLint收到投诉。您可以在类型定义中看到不同的类型:

// VoidOrUndefinedOnly is here to forbid any sneaky "Promise" returns.
// the actual return value is always a "DebugPromiseLike".
declare const UNDEFINED_VOID_ONLY: unique symbol;
// tslint:disable-next-line: void-return
type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never };
/**
 * Wrap any code rendering and triggering updates to your components into `act()` calls.
 *
 * Ensures that the behavior in your tests matches what happens in the browser
 * more closely by executing pending `useEffect`s before returning. This also
 * reduces the amount of re-renders done.
 *
 * @param callback An asynchronous, void callback that will execute as a single, complete React commit.
 *
 * @see https://reactjs.org/blog/2019/02/06/react-v16.8.0.html#testing-hooks
 */
// VoidOrUndefinedOnly is here to forbid any sneaky return values
export function act(callback: () => Promise<VoidOrUndefinedOnly>): Promise<undefined>;
/**
 * Wrap any code rendering and triggering updates to your components into `act()` calls.
 *
 * Ensures that the behavior in your tests matches what happens in the browser
 * more closely by executing pending `useEffect`s before returning. This also
 * reduces the amount of re-renders done.
 *
 * @param callback A synchronous, void callback that will execute as a single, complete React commit.
 *
 * @see https://reactjs.org/blog/2019/02/06/react-v16.8.0.html#testing-hooks
 */
export function act(callback: () => VoidOrUndefinedOnly): DebugPromiseLike;

// Intentionally doesn't extend PromiseLike<never>.
// Ideally this should be as hard to accidentally use as possible.
export interface DebugPromiseLike {
    // the actual then() in here is 1-ary, but that doesn't count as a PromiseLike.
    then(onfulfilled: (value: never) => never, onrejected: (reason: never) => never): never;
}

除了在async act中 Package fireEvent之外,你还可以经常使用waitFor来处理它后面的expect。我现在稍微喜欢这个。

fireEvent.click(await screen.findByTitle('A'));
    await waitFor(() => {
      expect(screen.getByAltText('B').getAttribute('src')).toEqual('C');
    })

相关问题