Bypass fetch call in React component

喜你入骨 提交于 2019-12-11 06:39:37

问题


I am attempting to write a test for a react component that sends out a fetch GET request after mounting. The test just needs to check if the component has rendered and when fetch runs I get this: ReferenceError: fetch is not defined. I have searched around and cant seem to find anything that will fix my problem. I am using jest and Test Utils for testing the components.

My component code:

export class Home extends Component {
    constructor(props) {
        ...
    }
    componentDidMount() {
        fetch('/some/path', {
            headers: {
                'Key1': 'Data1',
                'Key2': Data2
            }
        }).then(response => {
            if (response.status == 200) {
                response.json().then((data) => {
                    this.context.store.dispatch(setAssets(data))
                }
                );
            } else {
                return (
                    <Snackbar
                        open={true}
                        message={"ERROR: " + str(response.status)}
                        autoHideDuration={5000}
                    />
                );
            }
        }).catch(e => {});
           ...
        );
    }
    componentWillUnmount() {
        ...
    }
    logout(e) {
        ...
    }
    render() {
        return (
            <div>
                <AppBar
                    title="Title"
                    iconElementLeft={
                        <IconButton>
                            <NavigationClose />
                        </IconButton>
                    }
                    iconElementRight={
                        <IconMenu
                            iconButtonElement={
                                <IconButton>
                                    <MoreVertIcon />
                                </IconButton>
                            }
                            targetOrigin={{
                                horizontal: 'right',
                                vertical: 'top'
                            }}
                            anchorOrigin={{
                                horizontal: 'right',
                                vertical: 'top'
                            }}
                        >
                            <MenuItem>
                                Help
                            </MenuItem>
                        </IconMenu>
                    }
                />
                {
                    this.context.store.getState().assets.map((asset, i) => {
                        return (
                            <Card
                                title={asset.title}
                                key={i+1}
                            />
                        );
                    })
                }
            </div>
        );
    }
}

Home.contextTypes = {
    store: React.PropTypes.object
}

export default Home;

My Test Code:

var home

describe('Home', () => {
    beforeEach(() => {
        let store = createStore(assets);
        let a = store.dispatch({
                         type: Asset,
                         assets: [{
                                    'id': 1, 
                                    'title': 'TITLE'
                                 }],
                       });
        store.getState().assets = a.assets

        home = TestUtils.renderIntoDocument(
            <MuiThemeProvider muiTheme={getMuiTheme()}>
                <Provider store={store}>
                    <Home />
                </Provider>
            </MuiThemeProvider>
        );
    });
    it('renders the main page, including cards and appbar', () => {}

It errors when trying to render Home into document.

I have tried fetch-mock but this only allows for mock calls for API testing, which I'm not trying to do and doesn't mock the fetch calls in my component.

Mocking Home wont work because its the component I am trying to test. Unless there's a way to mock the componentDidMount() function that I have missed.

I just need a workaround for the fetch call. Any ideas??

EDIT: I'm using React's JSX for the component and JS for the test


回答1:


Try https://github.com/jhnns/rewire:

rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing

var fetchMock = { ... }

var rewire = require("rewire");
var myComponent = rewire("./myComponent.js");
myComponent.__set__("fetch", fetchMock);



回答2:


Unfortunately I'm using babel, which is listed as a limitation for rewire, but I tried it anyways...

I Added:

...
store.getState().assets = a.assets

var fetchMock = function() {return '', 200}
var rewire = require("rewire");
var HomeComponent = rewire('../Home.jsx');
HomeComponent.__set__("fetch", fetchMock);

home = TestUtils.renderIntoDocument(
    <MuiThemeProvider muiTheme={getMuiTheme()}>
        <Provider store={store}>
            <Home />
        ...

And received the error:

Error: Cannot find module '../Home.jsx'
   at Function.Module._resolveFilename (module.js:440:15)
   at internalRewire (node_modules/rewire/lib/rewire.js:23:25)
   at rewire (node_modules/rewire/lib/index.js:11:12)
   at Object.eval (Some/Path/Home-test.js:47:21)

I'm assuming this is because babel:

rename[s] variables in order to emulate certain language features. Rewire will not work in these cases

(Pulled from the link chardy provided me)

However the path isn't a variable, so I wonder if babel is really renaming it, and the path is 100% correct for my component's location. I don't think it's because I'm using JSX because it just cant find the component, its not a compatibility issue... Rewire still may not work even if it finds the file though, unfortunately, but I'd like to give it a shot all the same.




回答3:


I found an answer that worked for me, and it was very simple without including any other dependencies. It was a simple as storing the main function into a variable and overwriting it, then restoring the proper function after the testcase

SOLUTION:

var home

describe('Home', () => {
    const fetch = global.fetch

    beforeEach(() => {
        let store = createStore(assets);
        let a = store.dispatch({
                         type: Asset,
                         assets: [{
                                    'id': 1, 
                                    'title': 'TITLE'
                                 }],
                       });
        store.getState().assets = a.assets

        global.fetch = () => {return Promise.resolve('', 200)}

        home = TestUtils.renderIntoDocument(
            <MuiThemeProvider muiTheme={getMuiTheme()}>
                <Provider store={store}>
                    <Home />
                </Provider>
            </MuiThemeProvider>
        );
    });
    it('renders the main page, including cards and appbar', () => {
        ...
    });
    afterEach(() => {
        global.fetch = fetch;
    });
});



回答4:


Using the global context to store components can be brittle and is probably not a good idea for any sizable project.

Instead, you can use the dependency injection (DI) pattern which is a more formalized method for switching out different component dependencies (in this case, fetch) based on your runtime configuration. https://en.wikipedia.org/wiki/Dependency_injection

One tidy way to employ DI is by using an Inversion of Control (IoC) container, such as: https://github.com/jaredhanson/electrolyte




回答5:


What I did is I moved the fetch calls out of the components into a repository class then called that from within the component. That way the components are independent of the data source and I could just switch out the repository for a dummy repository in order to test or change the implementation from doing fetch to getting the data from localStorage, sessionStorage, IndexedDB, the file system, etc.

class Repository {
  constructor() {
    this.url = 'https://api.example.com/api/';
  }

  getUsers() {
    return fetch(this.url + 'users/').then(this._handleResponse);
  }

  _handleResponse(response) {
    const contentType = response.headers.get('Content-Type');
    const isJSON = (contentType && contentType.includes('application/json')) || false;

    if (response.ok && isJSON) {
      return response.text();
    }

    if (response.status === 400 && isJSON) {
      return response.text().then(x => Promise.reject(new ModelStateError(response.status, x)));
    }

    return Promise.reject(new Error(response.status));
  }
}

class ModelStateError extends Error {
  constructor(message, data) {
    super(message);
    this.name = 'ModelStateError';
    this.data = data;
  }

  data() { return this.data; }
}

Usage:

const repository = new Repository();
repository.getUsers().then(
  x => console.log('success', x),
  x => console.error('fail', x)
);

Example:

export class Welcome extends React.Component {
  componentDidMount() {
    const repository = new Repository();
    repository.getUsers().then(
      x => console.log('success', x),
      x => console.error('fail', x)
    );
  }

  render() {
    return <h1>Hello!</h1>;
  }
}


来源:https://stackoverflow.com/questions/38317062/bypass-fetch-call-in-react-component

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!