Write a jest test to my first component

2019-07-19 02:28发布

问题:

I just finished writing my first Reactjs component and I am ready to write some tests (I used material-ui's Table and Toggle). I read about jest and enzyme but I feel that I am still missing something.

My component looks like this (simplified):

export default class MyComponent extends Component {
    constructor() {
        super()
        this.state = {
            data: []
        }

        // bind methods to this
    }

    componentDidMount() {
        this.initializeData()
    }

    initializeData() {
        // fetch data from server and setStates
    }

    foo() {
        // manuipulatig data
    }

    render() {
        reutrn (
            <Toggle
                id="my-toggle"
                ...
                onToggle={this.foo}
            >
            </Toggle>

            <MyTable
                id="my-table"
                data={this.state.data}
                ...
            >
            </MyTable>
        )
    }
}

Now for the test. I want to write a test for the following scenario:

  1. Feed initializeData with mocked data.
  2. Toggle my-toggle
  3. Assert data has changed (Should I assert data itself or it is better practice to assert my-table instead?)

So I started in the very beginning with:

describe('myTestCase', () => {
    it('myFirstTest', () => {
        const wrapper = shallow(<MyComponent/>);
    }
})

I ran it, but it failed: ReferenceError: fetch is not defined

My first question is then, how do I mock initializeData to overcome the need of calling the real code that using fetch?


I followed this answer: https://stackoverflow.com/a/48082419/2022010 and came up with the following:

describe('myTestCase', () => {
    it('myFirstTest', () => {
        const spy = jest.spyOn(MyComponent.prototype, 'initializeData'
        const wrapper = mount(<MyComponent/>);
    }
})

But I am still getting the same error (I also tried it with componentDidMount instead of initializeData but it ended up the same).


Update: I was wrong. I do get a fetch is not defined error but this time it is coming from the Table component (which is a wrap for material-ui's Table). Now that I come to think about it I do have a lot of "fetches" along the way... I wonder how to take care of them then.

回答1:

fetch is supported in the browser, but jest/enzyme run in a Node environment, so fetch isn't a globally available function in your test code. There are a few ways you can get around this:

1: Globally mock fetch - this is probably the simplest solution, but maybe not the cleanest.

global.fetch = jest.fn().mockResolvedValue({
  json: () => /*Fake test data*/
  // or mock a response with `.text()` etc if that's what
  // your initializeData function uses
});

2: Abstract your fetch call into a service layer and inject that as a dependency - This will make your code more flexible (more boilerplate though), since you can hide fetch implementation behind whatever interface you choose. Then at any point in the future, if you decide to use a different fetch library, you can swap out the implementation in your service layer.

// fetchService.js
export const fetchData = (url) => {
  // Simplified example, only takes 'url', doesn't
  // handle errors or other params.
  return fetch(url).then(res => res.json());
}

// MyComponent.js
import {fetchService} from './fetchService.js'

class MyComponent extends React.Component {
  static defaultProps = {
    // Pass in the imported fetchService by default. This
    // way you won't have to pass it in manually in production
    // but you have the option to pass it in for your tests
    fetchService
  }
  ...
  initializeData() {
    // Use the fetchService from props
    this.props.fetchService.fetchData('some/url').then(data => {
      this.setState({ data });
    })
  }
}

// MyComponent.jest.js
it('myFirstTest', () => {
  const fetchData = jest.fn().mockResolvedValue(/*Fake test data*/);
  const fetchService = { fetchData };
  const wrapper = mount(<MyComponent fetchService={fetchService} />);
  return Promise.resolve().then(() = {
    // The mock fetch will be resolved by this point, so you can make
    // expectations about your component post-initialization here
  })
}