绕过 React 组件中的 fetch 调用

IT技术 reactjs testing fetch jsx jestjs
2021-05-22 13:58:34

我正在尝试为一个react组件编写一个测试,该组件在安装后发出一个 fetch GET 请求。测试只需要检查组件是否已呈现,当 fetch 运行时我得到这个:ReferenceError: fetch is not defined我四处搜索,似乎找不到任何可以解决我的问题的东西。我正在使用 jest 和 Test Utils 来测试组件。

我的组件代码:

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;

我的测试代码:

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', () => {}

尝试将 Home 渲染为文档时出错。

我尝试过fetch-mock ,但这仅允许模拟调用进行 API 测试,我不想这样做,也不会模拟组件中的 fetch 调用。

Mocking Home 不起作用,因为它是我要测试的组件。除非有办法模拟componentDidMount()我错过功能。

我只需要一个 fetch 调用的解决方法。有任何想法吗??

编辑:我使用 React 的 JSX 作为组件,使用 JS 进行测试

4个回答

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

rewire 为module添加了一个特殊的 setter 和 getter,因此您可以修改它们的行为以进行更好的单元测试

var fetchMock = { ... }

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

不幸的是,我正在使用 babel,它被列为 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)

我假设这是因为 babel:

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

(从 chardy 提供给我的链接中提取)

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

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

解决方案:

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

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

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

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