13

I have a stateless component:

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.
  );
}

It has two functions inside, handleShare and handleFavorite. I want to test these functions are called, and also that handleFavorite calls the prop function toggle favorite.

I tried wrapper.instance().handleFavorite(), but since it is a stateless component, it returns null.

Next someone on Stack Overflow suggested using a spy like so:

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

but this returned

'TypeError: Cannot read property 'equal' of undefined'.

What's the proper way to call these functions?

Lin Du
  • 88,126
  • 95
  • 281
  • 483
peter
  • 1,482
  • 4
  • 16
  • 38
  • 4
    Not an answer, just an observation that you're getting the `TypeError: Cannot read property 'equal' of undefined` because `.to.equal(true)` is `Chai` syntax. Since you are using `Jest` it should be `.toBe(true)`. – Brian Adams Mar 16 '19 at 03:08
  • Can you paste your test case here https://codesandbox.io/s/m50o1ok8o9 and send back url ? – Pawan Gangwani Mar 17 '19 at 19:04
  • Don't you need to get the instance first? wrapper.instance()? Not sure. That's the way I test functions on class components, but I'm having trouble with functions inside functional components because they're obviously not exposed. – HotFudgeSunday Oct 29 '19 at 18:11
  • How do these two functions trigger? Please edit your post, add more code – Lin Du Nov 12 '19 at 12:48

2 Answers2

1

You first need to think about what you want to test. Is it implementation details or the interaction with your component? The latter is a much better mindset and standpoint so what I would do is to test the component from the interaction point of view.

I would (for handleShare):

  1. Mock the Share object methods that are being called inside the share function;
  2. Select the button I want to click/touch
  3. Click/touch the button
  4. Assert that the methods were called.

Now for the handleFavorite:

  1. Mock AsyncStorage.getItem;
  2. Create a fake toggleFavorite function that I would pass as props;
  3. Select the button I want to click/touch
  4. Click/touch the button
  5. Assert my toggleFavorite function has been called

If you want to test these functions individually you would have to extract them to the outside of the component and test them individually. But I would not advise this as it is not clean and extra work.

Hope it helps!

Pedro Filipe
  • 995
  • 1
  • 8
  • 17
1

Functions within a functional component aren't defined on the prototype or the functional component instance, you cannot directly spy on them

The solution here is to test out the internal implementation of the individual functions

For instance for handleFavourite function you can mock AsynStorage and pass on a mock function for toggleFavourite and then asset it its called on TouchableOpacity onPress simulation

You can check how to mock AsyncStore in this post: 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();

Similarly you can test the individual functionalities within handleShare by first mocking Share.share and then checking against each condition.For instance you can add an spy on window.alert and see if that is called

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);
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • What if we want to test the return value of the function? For example `toCamelcase()` – johannchopin May 19 '20 at 08:19
  • @johannchopin In such cases, you would see that the return value is being used by some function of the other and would be used to update state or call parents function. The idea is then to not test the function which returns the value but to test the function which takes in the return value of this function. Write such a test case would make sure both of your functions are covered – Shubham Khatri May 19 '20 at 08:48
  • Yeah that's working for poor logic but what if I have multiple chained function like `toMyStringFormat()` that call `toMyCamelCasel()` that call `toMyLowercase()` it's not really accurate to only test the result of `toMyStringFormat()`. If the test failed we don't know exactly where the error come from – johannchopin May 20 '20 at 08:07
  • If you provide an answer for this question you will get the `bounty` :) – johannchopin May 22 '20 at 18:53
  • @johannchopin To be honest, I am not sure what we could do in this scenario. – Shubham Khatri May 24 '20 at 18:08
  • That was my fear, too. I don't think there's any solution. You still deserve the bounty for your answer, so here you go ;) – johannchopin May 25 '20 at 10:05
  • 1
    @johannchopin I hope there is a solution. Will be researching on it soon. Will let you know if I find anything – Shubham Khatri May 25 '20 at 10:06