【问题标题】:Jest / React - How to test the results of useState state update in a functionJest / React - 如何在函数中测试 useState 状态更新的结果
【发布时间】:2020-02-22 14:46:45
【问题描述】:

我有一个简单的购物车函数,当用户点击增加或减少购物车中商品的数量时,调用 useState 函数来更新购物车状态。

const [cart, setCart] = useState([]);

const onUpdateItemQuantity = (cartItem, quantityChange) => {
    const newCart = [...cart];
    const shouldRemoveFromCart = quantityChange === -1 && cartItem.count === 1;
    ...
    if (shouldRemoveFromCart) {
      newCart.splice(cartIndex, 1);
    } else {
      ...
    }
    setCart(newCart); //the useState function is called
}

所以开玩笑地说,我有一个函数可以测试用户何时将购物车项目的数量设置为零,但它尚未从购物车中删除该项目,我假设是因为它尚未收到 @ 的结果987654322@:

test('on decrement item from 1 to 0, remove from cart', () => {
      const [cartItemToDecrement] = result.current.cartItems;
      const productToDecrement = result.current.products.find(
        p => p.id === cartItemToDecrement.id
      );

      act(() => {
        result.current.decrementItem(cartItemToDecrement);
      });

      act(() => {
        result.current.decrementItem(cartItemToDecrement);
      });

      ...

      expect(result.current.cartItems).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            id: cartItemToDecrement.id,
            count: cartItemToDecrement.count - 2,
            inventory: productToDecrement.inventory + 2
          })
        ])
      ); 
    });
  });

此测试通过,因为购物车现在包含数量已降至零的商品。但实际上它不应该,因为从onUpdateItemQuantity 中的splice 操作我们的cartItems 数组现在根本不应该包含该对象。我如何在我的笑话测试中验证此删除正在发生(反应代码正常工作)。

【问题讨论】:

    标签: javascript reactjs unit-testing asynchronous jestjs


    【解决方案1】:

    我不太了解测试和您的onUpdateItemQuantity 之间的关系,因为您提供的上下文对我来说不够。

    但从您的问题来看,有 2 条线索可能有助于节省您的时间。

    1. 您可能知道 setCart 来自 useState 不是同步的,因此如果您尝试在同一帧从 useState 访问 cart,即使您确保它也不应该反映更改setCart 打来电话。 useEffect 是一个很好的解决方案。你可以找到useEffect[https://reactjs.org/docs/hooks-effect.html][1]的doc,任何变化都应该反映在useEffect,也许你可以把你的测试放在那里。

    2. 添加一个额外的变量myCart 来存储cart 和一个函数setMyCart。不要在代码中调用setCart,而是调用setMyCartsetMyCart 就像,

      function setMyCart(newCart)
      {
         myCart = newCart;
         setCart(myCart); // this is for triggering React re-render
      }
    

    然后使用myCart 可以立即反映更改进行测试。

    第二点附加代码的唯一目的是,当我们仍然依赖 React 的重新渲染机制时,我们使用我们自己的 Model myCart 用于我们的特定逻辑而不是 cart 状态来自React 不仅用于View,还用于我们在不适当场合的逻辑。

    【讨论】:

      【解决方案2】:

      测试状态变化是一项挑战,因为它不会产生任何特定的输出(除了影响渲染的任何东西)。

      关键是将测试范围缩小到你的职责,而不是测试 React。 React 负责确保useState 正确更新状态。您的责任是确保您正确使用数据并将正确的数据发回。因此,不要测试useState,而是测试您的组件如何响应给定的状态,并确保您设置了正确的状态。

      模拟useState 的答案。给它一个实现,返回传递给它的初始值,并带有一个模拟函数,让您可以读取正在保存的数据。

      这里有更详细的答案:

      https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga

      HTH!

      【讨论】:

        【解决方案3】:

        如果您需要使用 useState() 测试每个状态更新,您可以通过实现一个专门侦听该状态更改的 useEffect 函数来实现,在您的情况下是购物车:

        useEffect(() => {
            test(...)
        ), [cart]);
        

        因此您可以在 useEffect() 函数中进行测试。

        这将确保您拥有正确的购物车值,并且每次购物车更新时都会调用它。

        【讨论】:

          【解决方案4】:

          完全不清楚onUpdateItemQuantity 是如何被调用的。甚至它到底做了什么。无论如何,您声称toEqual(...[{...,count:cartItemToDecrement.count - 2,...}]...) 通过了。这可能意味着onUpdateItemQuantitydecrementItem内部被调用,在onUpdateItemQuantity内部有类似newCart[cartIndex].count--的东西,如果toEqual()通过,这意味着setState()成功更新状态,在@987654329之后@ 你已经实现了状态如果测试名称正确cartItemToDecrement.count === 1 的初始值应该意味着cartItemToDecrement.count - 2 === -1 并且你永远不会进入if (shouldRemoveFromCart)。所以也许问题不在于测试,而在于代码。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-08-24
            • 2021-01-16
            • 2019-04-01
            • 1970-01-01
            • 1970-01-01
            • 2023-01-18
            • 1970-01-01
            相关资源
            最近更新 更多