Enzyme async await mock function not being called

2019-05-22 14:40发布

问题:

I'm trying to test an async await function, however im getting an error.

● Should handle getGIF event › should handle getGIF event

expect(jest.fn()).toHaveBeenCalledTimes(1)

Expected mock function to have been called one time, but it was called zero times.

I'm unsure how to test async await functions, so i was using this blog as an example https://medium.com/@rishabhsrao/mocking-and-testing-fetch-with-jest-c4d670e2e167

App.js

import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Card from './Card';
import PropTypes from "prop-types";
const Styles = {
    marginTop: '100px',
    inputStyle: {
        borderRadius: '0px',
        border: 'none',
        borderBottom: '2px solid #000',
        outline: 'none',
        focus: 'none'
    }
}
class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            query: '',
            title: undefined,
            url: undefined
        }
        this.onChange = this.onChange.bind(this);
    }
    onChange(e) {
        this.setState({query: e.target.value})
    }
    // testing this function 
    getGIY = async(e) => {
        e.preventDefault();
        const { query } = this.state;
        await fetch(`http://api.giphy.com/v1/gifs/search?q=${query}&api_key=iBXhsCDYcnktw8n3WSJvIUQCXRqVv8AP&limit=5`)
        .then(response => response.json())
        .then(({ data }) => {
          this.setState({
            title: data[0].title,
            url: data[0].images.downsized.url
          });
        })
        .catch( (err) =>{
            console.log(err)
        });

    }
    render() {
        return (
            <div className="col-md-6 mx-auto" style={Styles}>
                <h1 className="gif-title">Random GIF fetch</h1>
                <form className="form-group" onSubmit={this.getGIY}>
                    <input
                        style={Styles.inputStyle}
                        className="form-control"
                        type="text"
                        value={this.state.query}
                        onChange={this.onChange}
                        placeholder="Search GIF..."/>
                    <button type="submit" className="btn btn-primary mt-4">Get GIF</button>
                </form>
                <Card title={this.state.title} url={this.state.url}/>
            </div>
        );
    }
}
PropTypes.propTypes = {
    onChange: PropTypes.func.isRequired,
    getGIY:PropTypes.func.isRequired,
    title:PropTypes.string.isRequired,
    url:PropTypes.string.isRequired
}
export default App;

App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import {shallow} from 'enzyme';
import App from './App';



describe('Should handle getGIF event', ()=> {
  it('should handle getGIF event', done => {
    const component = shallow(<App/>)

    const mockSuccessResponse = {};
    const mockJsonPromise = Promise.resolve(mockSuccessResponse);
    const mockQuery = "Owl"

    const mockFetchPromise = Promise.resolve({
      json:() => mockJsonPromise,

    });
    jest.spyOn(global, 'fetch').mockImplementation(()=> mockFetchPromise);

    expect(global.fetch).toHaveBeenCalledTimes(1);
    expect(global.fetch).toHaveBeenCalledWith(`http://api.giphy.com/v1/gifs/search?q=${mockQuery}&api_key=iBXhsCDYcnktw8n3WSJvIUQCXRqVv8AP&limit=5`);

    process.nextTick(() => { // 6
      expect(component.state()).toEqual({
        // ... assert the set state
      });

      global.fetch.mockClear(); // 7
      done(); // 8
    });

  })
})

回答1:

You can test it like this:

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

describe('Should handle getGIF event', () => {

  let mock, actualFetch;
  beforeEach(() => {
    mock = jest.fn();
    actualFetch = global.fetch;
    global.fetch = mock;
  });
  afterEach(() => {
    global.fetch = actualFetch;
  });

  it('should handle getGIF event', async () => {
    const component = shallow(<App />);
    component.setState({ query: 'Owl' });
    mock.mockResolvedValue({ 
      json: () => Promise.resolve({
        data: [{
          title: 'the title',
          images: { downsized: { url: 'the url' }}
        }]
      })
    });
    const form = component.find('form');

    await form.props().onSubmit({ preventDefault: () => {} });

    expect(mock).toHaveBeenCalledWith('http://api.giphy.com/v1/gifs/search?q=Owl&api_key=iBXhsCDYcnktw8n3WSJvIUQCXRqVv8AP&limit=5');  // Success!
    expect(component.state('title')).toBe('the title');  // Success!
    expect(component.state('url')).toBe('the url');  // Success!
  });
});

Details

fetch might not be defined in a Node.js environment so just grabbing whatever it was and replacing it with a mock, then restoring whatever it was is a good approach.

Use .setState to set the component state.

Use .find to get the form and use .props to access its props and call its onSubmit function.

Use an async test function and await the Promise returned by onSubmit so it is completely done before continuing.

Use .state to query the component state.