Jest mock and spy on an imported async function

2019-07-26 18:15发布

问题:

I am trying to figure out how to mock an imported properties function in Jest

This is my component AboutScreen.js

import React from 'react';
import { Constants, WebBrowser } from 'expo';
import { View, Text } from 'react-native';

const AboutScreen = () => { 
return (
  <View>
    <Text testId={"t-and-c"} onPress={() => WebBrowser.openBrowserAsync('tcUrl')}>
Terms & conditions
    </Text>
  </View>
  );
};


export default AboutScreen;

My test in AboutScreen.test.js looks like below

import React from 'react';
import { shallow } from 'enzyme';
import config from '../../config';
import AboutScreen from '../AboutScreen';
import { Constants, WebBrowser } from 'expo';
const { termsAndConditionsUrl, privacyPolicyUrl } = config;

jest.mock('expo', () => ({
  Constants: {
    manifest: {
        version: '0.0.1',
        releaseChannel: 'PROD',
    },
  WebBrowser: {
    openBrowserAsync: jest.fn()
    }
  },
 }));

it('click on terms and conditions link', () => {
   const mock = jest.spyOn(WebBrowser, 'openBrowserAsync'); 
   mock.mockImplementation(() => Promise.resolve());
   // above statement return 'Cannot spyOn on a primitive value; undefined given' 
   // WebBrowser.openBrowserAsync = jest.fn(); --> This returns `WebBroser undefined
   const wrapper = shallow(<AboutScreen />);
   wrapper.find({ testId: 't-and-c' }).simulate('click');
   expect(mock).lastCalledWith('abc');
   // expect(WebBrowser.openBrowserAsync).toHaveBeenCalledWith('tcUrl);
});

I was able to mock the Constants.manifest.version but unable to figure out how to mock the function within the 'Browser` object.

回答1:

You're close.


You are currently mocking WebBrowser to be a property inside of Constants, so that needs to be moved out like this:

jest.mock('expo', () => ({
  Constants: {
    manifest: {
      version: '0.0.1',
      releaseChannel: 'PROD',
    }
  },
  WebBrowser: {
    openBrowserAsync: jest.fn()
  }
}));

The other issue is how simulate works when using shallow. From the Common Gotchas section of the doc:

Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the event you give it. For example, .simulate('click') will actually get the onClick prop and call it.

...and since your component doesn't have an onClick property calling .simulate('click') ends up doing nothing.

This post from an Airbnb dev recommends invoking props directly and avoiding simulate.

You can invoke onPress by calling the prop directly like this:

wrapper.find({ testId: 't-and-c' }).props().onPress();

So all together the working test looks like this:

import React from 'react';
import { shallow } from 'enzyme';
import config from '../../config';
import AboutScreen from '../AboutScreen';
import { Constants, WebBrowser } from 'expo';
const { termsAndConditionsUrl, privacyPolicyUrl } = config;

jest.mock('expo', () => ({
  Constants: {
    manifest: {
      version: '0.0.1',
      releaseChannel: 'PROD',
    }
  },
  WebBrowser: {
    openBrowserAsync: jest.fn()
  }
}));

it('click on terms and conditions link', () => {
  const mock = jest.spyOn(WebBrowser, 'openBrowserAsync');
  mock.mockImplementation(() => Promise.resolve());

  const wrapper = shallow(<AboutScreen />);

  wrapper.find({ testId: 't-and-c' }).props().onPress();
  expect(mock).toHaveBeenCalledWith('tcUrl'); // Success!
});