Bypass fetch call in React component

时间:2016-07-11 22:15:41

标签: reactjs testing fetch jsx jestjs

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

5 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

不幸的是我正在使用babel,它被列为limitation for rewire,但我还是试过了......

我补充说:

...
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 />
        ...

收到错误:

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)

我假设这是因为巴贝尔:

  

重命名[s]变量以模拟某些语言功能。 Rewire在这些情况下不起作用

(从chardy提供给我的链接中拉出来)

然而路径不是变量,所以我想知道babel是否真的重命名它,并且路径对于我的组件的位置是100%正确的。我不认为这是因为我使用的是JSX,因为它无法找到组件,它不是兼容性问题......即使它找到了文件,Rewire仍然可能无法工作,不幸的是,但是我想给这一切都是一样的。

答案 2 :(得分:0)

我找到了一个对我有用的答案,并且在没有包含任何其他依赖项的情况下非常简单。将main函数存储到变量并覆盖它,然后在测试用例之后恢复正确的函数,这很简单

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

答案 3 :(得分:0)

使用全局上下文存储组件可能很脆弱,对于任何大型项目来说可能都不是一个好主意。

相反,您可以使用依赖注入(DI)模式,这是一种更正式的方法,可根据您的运行时配置切换出不同的组件依赖关系(在本例中为fetch)。 https://en.wikipedia.org/wiki/Dependency_injection

使用DI的一种简洁方法是使用控制反转(IoC)容器,例如: https://github.com/jaredhanson/electrolyte

答案 4 :(得分:0)

我所做的是将fetch调用从组件中移出到存储库类中,然后从组件中调用它。这样,组件独立于数据源,我可以只为虚拟存储库切换存储库,以便测试或更改从获取到从localStoragesessionStorage获取数据的实现, IndexedDB,文件系统等

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

用法:

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

示例:

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