使用酶测试无状态React组件中的功能

时间:2019-03-15 19:21:39

标签: reactjs react-native jestjs enzyme

我有一个无状态组件:

export default function TripReportFooter(props) {
  const { tripReport, user, toggleFavorite, navigation } = props;


  handleShare = async slug => {
    try {
      const result = await Share.share({
        message: `Check out this Trip Report:\n/p/${slug}/`
      });

      if (result.action === Share.sharedAction) {
        if (result.activityType) {
        } else {
          // shared
        }
      } else if (result.action === Share.dismissedAction) {
      }
    } catch (error) {
      alert(error.message);
    }
  };

  handleFavorite = async id => {
    const token = await AsyncStorage.getItem("token");
    toggleFavorite(id, token);
  };

  return (
    ... // handleFavorite and handleShare called with TouchableOpacities.
  );
}

它内部有两个功能,handleShare和handleFavorite。我要测试这些函数是否被调用,以及那个handleFavorite调用prop函数是否切换到收藏夹。

我尝试了wrapper.instance()。handleFavorite(),但是由于它是一个无状态组件,因此它返回null。

接下来,Stack Overflow上的某人建议使用这样的间谍:

    wrapper = shallow(<TripReportFooter {...props} handleFavorite={press} />);
    wrapper
      .find("TouchableOpacity")
      .at(0)
      .simulate("press");
    expect(press.called).to.equal(true);
  });

,但这返回了'TypeError:无法读取未定义的属性'equal'。调用这些函数的正确方法是什么?

3 个答案:

答案 0 :(得分:1)

您首先需要考虑要测试的内容。是实施细节还是与组件的交互?后者是一种更好的心态和立场,所以我要做的是从交互的角度测试该组件。

我会(对于handleShare):

  1. 模拟在share函数内部被调用的Share对象方法;
  2. 选择我要单击/触摸的按钮
  3. 单击/触摸按钮
  4. 确认已调用方法。

现在handleFavorite

  1. 模拟AsyncStorage.getItem;
  2. 创建一个我将作为道具传递的假toggleFavorite函数;
  3. 选择我要单击/触摸的按钮
  4. 单击/触摸按钮
  5. 确认我的toggleFavorite函数已被调用

如果要单独测试这些功能,则必须将它们提取到组件外部并分别进行测试。但是我不建议这样做,因为这不干净而且没有额外的工作。

希望有帮助!

答案 1 :(得分:1)

功能组件中的功能未在原型或功能组件实例上定义,您无法直接对其进行监视

这里的解决方案是测试各个功能的内部实现

例如,对于handleFavourite函数,您可以模拟AsynStorage并将模拟函数传递给toggleFavourite,然后在TouchableOpacity onPress模拟中将其作为资产

您可以在这篇文章中检查如何模拟AsyncStore: How to test Async Storage with Jest?

const mocktToggleFavourite = jest.fn();
wrapper = shallow(<TripReportFooter {...props} toggleFavourite={mocktToggleFavourite} />);
    wrapper
      .find("TouchableOpacity")
      .at(0)
      .simulate("press");
    expect(mockToggleFavourite).toHaveBeenCalled();

类似地,您可以通过首先模拟handleShare然后检查每个条件来测试Share.share中的各个功能。例如,您可以在window.alert上添加一个间谍,看看是否被称为

const windowSpy = jest.spyOn(window, 'alert');
wrapper = shallow(<TripReportFooter {...props} toggleFavourite={mocktToggleFavourite} />);
//Simulate event that calls handleShare
// Mock Share to give required result
expect(windowSpy).toBeCalledWith(expectedValue);

答案 2 :(得分:0)

在上面的代码中,您将press函数作为handleFavorite道具传递,但是您没有使用handleFavorite道具。为了在这种情况下使用间谍程序(调用该功能的一个不错的选择),您需要将handleFavorite函数从组件中拉出。

jestjs提供了一个称为模拟功能的东西,它基本上是spysinonjs的间谍具有类似的api)。

Jest Mock Functions

这里有一些sudo代码来描述我的意思:

// TripReporterFooter.js

import React from 'react';

const TripleReportFooter = (props) => {
    return (
        <TouchableOpacity onShare={props.handleShare} onFavorite={props.handleFavorite} />
    );
};

然后可以将handleSharehandleFavorite函数作为模拟从测试中传递。

// TripReportFooter test
describe('TripReportFooter', () => {
    it('calls handleFavorite', () => {
        const mockHandleFavorite = jest.fn();

        wrapper = shallow(<TripReportFooter handleFavorite={mockHandleFavorite} />);

        wrapper
            .find('TouchableOpacity')
            .at(0)
            .simulate('press');

        expect(mockHandleFavorite.mock.calls.length).toBe(1);
    });
});

使用上述代码,您将需要从handleFavorite中提取handleShareTripleReporterFooter实现,并将它们传递到呈现<TripleReporterFooter />的任何地方。

例如:

import { handleShare, handleFavorite } from './shareAndFav'`
const TripleReporterParent = () => {
  return <TripleReportFooter handleShare={handleShare} handleFavorite={handleFavorite} />
}

如果要在两个组件之间共享功能,可以始终将它们从组件中拉出到共享模块中。这是在文件中创建并在两个组件之间共享的那些函数的示例:

// A new file called shareAndFav.js (Same as the name from the TripleReporterParent).
const handleShare = async slug => {
    try {
      const result = await Share.share({
        message: `Check out this Trip Report:\n/p/${slug}/`
      });

      if (result.action === Share.sharedAction) {
        if (result.activityType) {
        } else {
          // shared
        }
      } else if (result.action === Share.dismissedAction) {
      }
    } catch (error) {
      alert(error.message);
    }
  };

  handleFavorite = async id => {
    const token = await AsyncStorage.getItem("token");
    toggleFavorite(id, token);
  };

export { handleShare, handleFavorite } // the import from TripleReportFooterParent components can now reuse these

它们不一定需要在呈现器中(它们只是函数,您可以像上面一样在它们自己的文件中定义它们)。

将它们拉入自己的模块后,可以通过导入它们在所需的任何组件中使用它们:

import { handleShare, handleFavorite } from '<the path to shareAndFav.js>'