我有两个简单的组成部分.我正在使用JEST为他们编写测试.我想测试一下,当我单击CurrencyItem时,应该将相应的数组添加到 Select 状态.正如您所看到的,我将handleCurrencyToggle个函数作为props 传递给CurrencyItem,因此我也想在JEST测试中模拟它.

CurrencySelector.tsx

import { useEffect, useState } from "react";
import { currencies } from "../constants";
import { Currency } from "../types";
import CurrencyItem from "./CurrencyItem";

const CurrencySelector = () => {
  const initialSelected = () => {
    const storedCurrencies = sessionStorage.getItem("currencies");
    return storedCurrencies ? JSON.parse(storedCurrencies) : [];
  };

  const [selected, setSelected] = useState<Currency[]>(initialSelected);

  const handleCurrencyToggle = (currency: Currency) => {
    const isSelected = selected.some(
      (selectedCurrency) => selectedCurrency.name === currency.name
    );

    if (isSelected) {
      setSelected((prevSelected) =>
        prevSelected.filter(
          (selectedCurrency) => selectedCurrency.name !== currency.name
        )
      );
    } else {
      setSelected((prevSelected) => [...prevSelected, currency]);
    }
  };

  useEffect(() => {
    sessionStorage.setItem("currencies", JSON.stringify(selected));
  }, [selected]);

  return (
    <div className="currency-selector">
      <div className="selected-currencies currency-items">
        {selected.length > 0 ? (
          selected.map((currency: Currency, index: number) => (
            <div
              data-testid="selected-currency"
              key={index}
              className="selected-currency currency"
            >
              <span>{currency.name}</span>
              <button
                className="unselect-currency"
                onClick={() => handleCurrencyToggle(currency)}
              >
                x
              </button>
            </div>
          ))
        ) : (
          <div data-testid="no-selected-currencies" className="no-currencies">
            No selected currencies
          </div>
        )}
      </div>
      <div className="currency-items">
        {currencies.map((currency: Currency, index: number) => (
          <CurrencyItem
            data-testid={`currency-item-${index}`}
            key={index}
            currency={currency}
            onClick={() => handleCurrencyToggle(currency)}
            isSelected={selected.some(
              (selectedCurrency) => selectedCurrency.name === currency.name
            )}
          />
        ))}
      </div>
    </div>
  );
};

export default CurrencySelector;

CurrencyItem.tsx

import { Currency } from "../types";

const CurrencyItem = ({
  currency,
  onClick,
  isSelected,
}: {
  currency: Currency;
  onClick: () => void;
  isSelected: boolean;
}) => {
  const handleCheckboxChange = () => {
    onClick();
  };

  return (
    <div data-testid="currency-item" onClick={onClick} className="currency">
      <label>
        <input onChange={handleCheckboxChange} checked={isSelected} type="checkbox" />
        <span className="checkbox-container"></span>
      </label>
      <span>{currency.name}</span>
    </div>
  );
};

export default CurrencyItem;

货币数组:

import { Currency } from "../types";

export const currencies: Currency[] = [
  {
    name: "EUR",
  },
  {
    name: "PLN",
  },
  {
    name: "GEL",
  },
  {
    name: "DKK",
  },
  {
    name: "CZK",
  },
  {
    name: "GBP",
  },
  {
    name: "SEK",
  },
  {
    name: "USD",
  },
  {
    name: "RUB",
  },
];

我的测试是:

  it("adds currency to select list", () => {
    const handleCurrencyToggle = jest.fn()
    const { getByTestId: getByTestId2 } = render(
        <CurrencySelector />
      );
    const { getByTestId } = render(
      <CurrencyItem
        key={1}
        currency={currencies[0]}
        onClick={() => () => {
          handleCurrencyToggle();
        }}
        isSelected={true}
      />
    );
    
    const currencyItem = getByTestId("currency-item");
    fireEvent.click(currencyItem);

    const selectItem = getByTestId2("selected-currency").textContent
    expect(selectItem).toBe("EUR");
  });
});

我知道这是不正确的,但请指导我如何模拟真正的handleCurrencyTogger函数.

推荐答案

要测试某些东西,您需要首先分配职责.将对象分开并分别进行测试.

根据您的示例,您的CurrencySelector组件有太多的责任.它访问本地存储,它知道选定的货币存储在哪里以及如何存储.你得把它拿走.

要做到这一点,最好的方法是拥有一些定制的挂钩.这样,您可以单独模拟事物,并单独测试它们.

您可能需要使用处理所有本地存储操作的useLocalStorage挂钩. 下面是一些代码:

const useLocalStorage = <T>(key: string, defaultValue?: T) => {
  return {
    setData: (data: T) => localStorage.setItem(key, JSON.stringify(data)),
    getData: () => {
      const data = localStorage.getItem(key);
      if (!data) {
        return defaultValue || undefined;
      }

      return JSON.parse(data) as T;
    },
  };
};

此后,设置和获取选定货币的所有逻辑可能会被隔离到它自己的钩子中,如下所示:

import { useCallback, useEffect, useState } from 'react';
import { Currency } from '../Curencies/types';

export const useSelectedCurrency = () => {
  const { setData, getData } = useLocalStorage<Array<Currency>>(
    'currencies',
    []
  );
  const [selected, setSelected] = useState<Array<Currency>>(getData() || []);

  // keeps local storage in sync
  useEffect(() => {
    setData(selected);
  }, [selected]);

  // using useCallback hook to memoize the function reference
  const toggleCurrency = useCallback(
    (currency: Currency) => {
      const isSelected = selected.some(
        (selectedCurrency) => selectedCurrency.name === currency.name
      );

      if (isSelected) {
        setSelected((prevSelected) =>
          prevSelected.filter(
            (selectedCurrency) => selectedCurrency.name !== currency.name
          )
        );
      } else {
        setSelected((prevSelected) => [...prevSelected, currency]);
      }
    },
    [selected]
  );

  return {
    selectedCurrencies: selected,
    toggleCurrency,
  };
};

现在,该组件仅依赖于自定义挂钩


const CurrencySelector = () => {
  const { selectedCurrencies, toggleCurrency } = useSelectedCurrency();

  return (
    <div className="currency-selector">
      <div className="selected-currencies currency-items">
        {selectedCurrencies.length > 0 ? (
          selectedCurrencies.map((currency: Currency, index: number) => (
            <div
              data-testid="selected-currency"
              key={index}
              className="selected-currency currency"
            >
              <span>{currency.name}</span>
              <button
                className="unselect-currency"
                onClick={() => toggleCurrency(currency)}
              >
                x
              </button>
            </div>
          ))
        ) : (
          <div data-testid="no-selected-currencies" className="no-currencies">
            No selected currencies
          </div>
        )}
      </div>
      <div className="currency-items">
        {selectedCurrencies.map((currency: Currency, index: number) => (
          <CurrencyItem
            data-testid={`currency-item-${index}`}
            key={index}
            currency={currency}
            onClick={() => toggleCurrency(currency)}
            isSelected={selectedCurrencies.some(
              (selectedCurrency) => selectedCurrency.name === currency.name
            )}
          />
        ))}
      </div>
    </div>
  );
};

我们只需要测试切换逻辑的钩子:

jest.mock('./useLocalStorage', () => ({
  useLocalStorage: jest.fn().mockReturnValue({
    getData: () => [],
    setData: () => {},
  }),
}));

const useLocalStroageMock = useLocalStorage as jest.MockedFunction<
  typeof useLocalStorage
>;

const getCurrencies = (...names: Array<string>): Array<Currency> =>
  names.map((name) => ({ name }));

describe('useSelectedCurrency', () => {
  it('Should return empty array if empt arry if nothig in the local storage', () => {
    const renderResult = renderHook(useSelectedCurrency);

    expect(renderResult.result.current.selectedCurrencies).toEqual([]);
  });

  it(`Should return the items form the local storage as default`, () => {
    const currencies = getCurrencies('USD', 'EUR');
    useLocalStroageMock.mockReturnValue({
      getData: jest.fn().mockReturnValue(currencies),
      setData: jest.fn(),
    });

    const renderResult = renderHook(useSelectedCurrency);

    expect(renderResult.result.current.selectedCurrencies).toEqual(currencies);
  });

  it(`Should remove a currency if a togggle is called iwht name in the selected. t should also remove it from local storage`, () => {
    const currencies = getCurrencies('USD', 'EUR');
    const setDataMock = jest.fn();
    useLocalStroageMock.mockReturnValue({
      getData: jest.fn().mockReturnValue(currencies),
      setData: setDataMock,
    });

    const renderResult = renderHook(useSelectedCurrency);

    act(() => renderResult.result.current.toggleCurrency({ name: 'USD' }));

    expect(renderResult.result.current.selectedCurrencies).toEqual(
      getCurrencies('EUR')
    );
    expect(setDataMock).toHaveBeenCalledWith(getCurrencies('EUR'));
  });
});

Link to a working example here

Javascript相关问答推荐

如何修复内容安全策略指令脚本-SRC自身错误?

vscode扩展-webView Panel按钮不起任何作用

如何在Angular中插入动态组件

按下同意按钮与 puppeteer 师

使用i18next在React中不重新加载翻译动态数据的问题

如何在Obsidian dataview中创建进度条

Websocket错误—有一个或多个保留位开启:reserved1 = 1,reserved2 = 0,reserved3 = 0

编辑文本无响应.onClick(扩展脚本)

在forEach循环中获取目标而不是父对象的属性

使用js构造一个html<;ath&>元素并不能使其正确呈现

为什么客户端没有收到来自服务器的响应消息?

元素字符串长度html

为什么当我更新数据库时,我的所有组件都重新呈现?

按特定顺序将4个数组组合在一起,按ID分组

我的NavLink活动类在REACT-ROUTER-V6中出现问题

bootstrap S JS赢得了REACT中的函数/加载

JSON Web令牌(JWT)错误:RSA密钥对的签名无效

在JS/TS中构造RSA公钥

ReactJS Sweep Line:优化SciChartJS性能,重用wasmContext进行多图表渲染

单击子按钮时将活动切换类添加到父分区