在React中使用Jest和MSW测试GraphQL Fetch请求:完整指南

在React中使用Jest和MSW测试GraphQL Fetch请求:完整指南

本文详细阐述了在React应用中使用Jest和MSW测试GraphQL fetch请求时遇到的两个常见问题及其解决方案。首先,针对Jest测试环境(Node)中fetch未定义错误,引入isomorphic-fetch进行polyfill。其次,为了确保MSW能正确拦截请求,指导如何将fetch请求URL修改为绝对路径,并利用GraphQL操作名称进行MSW处理器匹配。通过这些步骤,可实现GraphQL请求的可靠模拟测试。

在现代react应用开发中,使用fetch api与graphql后端进行数据交互是常见模式。为了确保代码质量和功能正确性,编写单元测试至关重要。jest作为流行的javascript测试框架,结合msw(mock service worker)进行网络请求模拟,能够提供强大的测试能力。然而,在实际操作中,开发者可能会遇到一些挑战,特别是当测试环境为node.js时。本教程将深入探讨在jest和msw环境下测试graphql fetch请求时可能遇到的两个核心问题,并提供详细的解决方案。

1. 解决 ReferenceError: fetch is not defined 错误

当在Jest测试环境中运行包含fetch API调用的代码时,你可能会遇到ReferenceError: fetch is not defined的错误。这是因为Jest默认的测试环境是Node.js,而Node.js环境中原生并不提供浏览器fetch API。

问题原因:Jest的默认testEnvironment配置为node。在Node环境中,全局对象(global)不包含fetch函数,因此当你的代码尝试调用fetch时,就会抛出ReferenceError。

解决方案:为了在Node环境中模拟fetch API,我们需要引入一个polyfill。isomorphic-fetch是一个常用的选择,它提供了在Node和浏览器环境中都能工作的fetch实现。

集成步骤:

安装 isomorphic-fetch:

npm install isomorphic-fetch --save-dev# 或者 yarn add isomorphic-fetch --dev

在 setupTests.js 中引入:setupTests.js(或你的Jest setup文件,例如jest.setup.js)是在每个测试文件运行前执行的脚本,是引入全局polyfill的理想位置。

// setupTests.jsimport '@testing-library/jest-dom'; // 如果你使用了react-testing-libraryimport 'isomorphic-fetch'; // 引入fetch polyfill// MSW setupimport { server } from './src/__mocks__/server';beforeAll(() => server.listen());afterEach(() => server.resetHandlers());afterAll(() => server.close());

通过在setupTests.js中引入isomorphic-fetch,fetch函数将在所有测试运行前被全局定义,从而解决ReferenceError。

2. 确保 MSW 正确拦截 GraphQL 请求

即使fetch错误解决了,你可能还会发现MSW未能按预期拦截GraphQL请求,导致测试仍然尝试执行真实的网络请求,或者返回undefined等非预期结果。这通常与MSW在Node环境下的请求URL匹配机制以及GraphQL处理器配置有关。

问题原因:

Node环境中的URL匹配: 在Node.js环境中,MSW在拦截请求时对URL的匹配更为严格。如果fetch请求使用的是相对URL(例如/graphql),MSW可能无法正确识别和拦截它,因为它期望一个完整的、绝对的URL。GraphQL处理器匹配: MSW的graphql.query或graphql.mutation处理器默认通过GraphQL操作名称(Operation Name)来匹配请求,而不是通过URL路径。

解决方案:

在 fetch 请求中使用绝对URL:修改你的数据获取函数,使其在fetch调用中使用一个绝对URL。这个URL可以是任意有效的URL,只要它能被MSW识别即可。例如,你可以使用http://localhost/graphql或http://whatever.com/graphql。在测试环境中,这个URL并不会真正被访问,因为它会被MSW拦截。

// src/store/favorites/utils.js (修改后)export const FAVORITES_QUERY = `  query Favorites {    favorites  }`;export async function fetchFavorites() {  // 注意:这里将相对URL "/graphql" 修改为绝对URL  return fetch("http://whatever.com/graphql", {    method: "POST",    headers: {      "Content-Type": "application/json"    },    body: JSON.stringify({ query: FAVORITES_QUERY })  })    .then((resp) => resp.json())    .then((result) => {      return result.data && result.data.favorites ? result.data.favorites : [];    })    .catch((err) => {      console.error("Error fetching favorites:", err); // 添加错误日志      return undefined;    });}

MSW处理器通过GraphQL操作名称匹配:确保你的MSW graphql处理器使用GraphQL操作名称进行匹配。GraphQL操作名称是在GraphQL查询字符串中定义的,例如query Favorites中的Favorites。

// src/__mocks__/handlers.js (修改后)import { graphql } from 'msw';// 注意:FAVORITES_QUERY 不再直接用于匹配,而是其操作名称 'Favorites'// import { FAVORITES_QUERY } from '../store/favorites/utils'; export const handlers = [  // 使用GraphQL操作名称 'Favorites' 进行匹配  graphql.query('Favorites', (req, res, ctx) => {    return res(      ctx.data({        favorites: [1], // 返回模拟数据      }),    );  }),  // 你可以添加其他GraphQL查询或变更的处理器  // graphql.mutation('AddFavorite', (req, res, ctx) => { /* ... */ }),];

通过以上修改,fetchFavorites函数发出的请求将使用一个绝对URL,并且MSW的graphql.query处理器将通过匹配GraphQL操作名称Favorites来拦截该请求,返回预设的模拟数据。

完整示例代码结构与配置

为了更好地理解上述解决方案,以下是结合了所有修改后的文件结构和代码示例:

my-project    src        __mocks__            server.js            handlers.js        store            favorites                __tests__                    utils.test.js                utils.js    setupTests.js    jestConfig.js

jestConfig.js

module.exports = {  // transform配置保持不变,用于处理JSX/TSX  transform: {    '^.+.jsx?$': 'babel-jest',    // 如果使用TypeScript,可能还需要配置ts-jest    // '^.+.tsx?$': 'ts-jest',  },  // 监听插件  watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],  // 在测试环境设置文件后运行  setupFilesAfterEnv: ['./setupTests.js'],  // 确保testEnvironment是node,如果需要模拟浏览器DOM,则设为jsdom  // testEnvironment: 'jsdom', // 如果需要模拟浏览器DOM};

setupTests.js

import '@testing-library/jest-dom'; // 推荐,如果使用react-testing-libraryimport 'isomorphic-fetch'; // 解决 Node 环境中 fetch 未定义的问题import { server } from './src/__mocks__/server';// MSW 服务器生命周期管理beforeAll(() => server.listen()); // 在所有测试开始前启动 MSW 服务器afterEach(() => server.resetHandlers()); // 在每个测试后重置处理器,清除之前测试的影响afterAll(() => server.close()); // 在所有测试结束后关闭 MSW 服务器

src/__mocks__/server.js

import { handlers } from "./handlers";import { setupServer } from "msw/node";export const server = setupServer(...handlers);

src/__mocks__/handlers.js

import { graphql } from 'msw';export const handlers = [  // 拦截名为 'Favorites' 的 GraphQL 查询操作  graphql.query('Favorites', (req, res, ctx) => {    return res(      ctx.data({        favorites: [1], // 返回模拟数据      }),    );  }),];

src/store/favorites/utils.js

export const FAVORITES_QUERY = `query Favorites{  favorites}`;export async function fetchFavorites() {  // 使用绝对URL进行 fetch 请求,确保 MSW 能够拦截  return fetch("http://whatever.com/graphql", {    method: "POST",    headers: {      "Content-Type": "application/json"    },    body: JSON.stringify({ query: FAVORITES_QUERY })  })    .then((resp) => resp.json())    .then((result) => {      // 健壮性检查,确保 result.data 和 result.data.favorites 存在      return result.data && result.data.favorites ? result.data.favorites : [];    })    .catch((err) => {      console.error("Error fetching favorites:", err); // 打印错误以便调试      return undefined;    });}

src/store/favorites/__tests__/utils.test.js

import { fetchFavorites } from '../utils';describe('fetchFavorites', () => {  it('should fetch favorites successfully', async () => {    const mockFavorites = [1];    // 调用 fetchFavorites,它将发出一个被 MSW 拦截的请求    const favorites = await fetchFavorites();    // 断言返回的数据与模拟数据一致    expect(favorites).toEqual(mockFavorites);  });  // 可以添加其他测试用例,例如错误处理  it('should handle fetch errors gracefully', async () => {    // 假设你有一个方法可以模拟服务器错误响应    // server.use(    //   graphql.query('Favorites', (req, res, ctx) => {    //     return res(ctx.errors([{ message: 'Something went wrong' }]));    //   })    // );    // const favorites = await fetchFavorites();    // expect(favorites).toBeUndefined(); // 或者其他你期望的错误处理结果  });});

总结与注意事项

通过遵循上述步骤,你将能够成功地在Jest和MSW环境中测试你的React应用中的GraphQL fetch请求。

关键点回顾:

fetch Polyfill: 在Node.js测试环境中,必须使用isomorphic-fetch等polyfill来提供fetch API。绝对URL: 在测试中,fetch请求应使用绝对URL,以便MSW在Node环境中能够可靠地拦截。GraphQL操作名称匹配: MSW的graphql处理器通过GraphQL查询或变更的“操作名称”进行匹配,而不是URL路径。确保你的处理器配置正确。MSW生命周期: 在setupTests.js中正确设置beforeAll, afterEach, afterAll钩子,以确保MSW服务器的正确启动、重置和关闭。

遵循这些最佳实践,你将能够构建健壮、可靠的单元测试,有效模拟网络请求,从而专注于业务逻辑的测试,提高开发效率和代码质量。

以上就是在React中使用Jest和MSW测试GraphQL Fetch请求:完整指南的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1520053.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 12:58:37
下一篇 2025年12月20日 12:58:42

相关推荐

  • SASS 中的 Mixins

    mixin 是 css 预处理器提供的工具,虽然它们不是可以被理解的函数,但它们的主要用途是重用代码。 不止一次,我们需要创建多个类来执行相同的操作,但更改单个值,例如字体大小的多个类。 .fs-10 { font-size: 10px;}.fs-20 { font-size: 20px;}.fs-…

    2025年12月24日
    000
  • TypeScript 中如何约束对象为 CSS 属性?

    typescript 中如何约束对象为 css 属性 想要约束一个对象为 css 属性,以便在调用函数时得到自动补全提示,可以采用以下方法: 使用 react 的 cssproperties 类型 对于 react 项目,可以使用 react 提供的 cssproperties 类型: 立即学习“前…

    2025年12月24日
    300
  • 如何在 TypeScript 中约束对象为 CSS 属性?

    如何在 typescript 中约束对象为 css 属性? 在 typescript 中,为特定目的而约束对象类型是很重要的。在本文中,我们将探究如何将对象约束为包含 css 属性。 考虑以下函数: function setattrstoelement(el: htmlelement, attr: …

    2025年12月24日
    000
  • 如何使用 TypeScript 约束对象以匹配 CSS 属性?

    如何约束 typescript 对象以匹配 css 属性? setattrstoelement 函数接收两个参数,其中第二个参数应为 css 属性。对于 react 项目,可以使用 cssproperties 类型: import { cssproperties } from “react”;fun…

    2025年12月24日
    000
  • 为什么使用 :global 修改 Antd 样式无效?

    :global 修改 antd 样式为何无效 本文旨在帮助您解决在组件内使用:global修改 antd 全局样式未生效的问题。 问题描述 您在组件内使用:global修改 antd 按钮样式,但没有生效。完整代码可参考 https://codesandbox.io/s/fk7jnl 。 解决方案 …

    2025年12月24日
    000
  • 为什么在 React 组件中无法获得 Tailwind CSS 语法提示?

    为什么在 React 组件中无法获得 Tailwind CSS 语法提示? 你在 VSCode 中编写 HTML 文件时,可以正常获取 Tailwind CSS 语法提示。但当你尝试在 React 组件中编写 Tailwind CSS 时,这些提示却消失不见了。这是什么原因造成的? 解决方案 要解决…

    2025年12月24日
    000
  • 如何在 VSCode 中为 React 组件启用 Tailwind CSS 提示?

    在 vscode 中为 react 组件启用 tailwind css 提示 如果你在使用 vscode 编写 react 组件时,发现 tailwind css 提示无法正常显示,这里有一个解决方法: 安装 tailwind css intellisense 插件 这是实现代码提示的关键,确保你已…

    2025年12月24日
    200
  • React 或 Vite 是否会自动加载 CSS?

    React 或 Vite 是否自动加载 CSS? 在 React 中,如果未显式导入 CSS,而页面却出现了 CSS 效果,这可能是以下原因造成的: 你使用的第三方组件库,例如 AntD,包含了自己的 CSS 样式。这些组件库在使用时会自动加载其 CSS 样式,无需显式导入。在你的代码示例中,cla…

    2025年12月24日
    000
  • React 和 Vite 如何处理 CSS 加载?

    React 或 Vite 是否会自动加载 CSS? 在 React 中,默认情况下,使用 CSS 模块化时,不会自动加载 CSS 文件。需要手动导入或使用 CSS-in-JS 等技术才能应用样式。然而,如果使用了第三方组件库,例如 Ant Design,其中包含 CSS 样式,则这些样式可能会自动加…

    2025年12月24日
    000
  • ElementUI el-table 子节点选中后为什么没有打勾?

    elementui el-table子节点选中后没有打勾? 当您在elementui的el-table中选择子节点时,但没有出现打勾效果,可能是以下原因造成的: 在 element-ui 版本 2.15.7 中存在这个问题,升级到最新版本 2.15.13 即可解决。 除此之外,请确保您遵循了以下步骤…

    2025年12月24日
    200
  • 您不需要 CSS 预处理器

    原生 css 在最近几个月/几年里取得了长足的进步。在这篇文章中,我将回顾人们使用 sass、less 和 stylus 等 css 预处理器的主要原因,并向您展示如何使用原生 css 完成这些相同的事情。 分隔文件 分离文件是人们使用预处理器的主要原因之一。尽管您已经能够将另一个文件导入到 css…

    2025年12月24日
    000
  • CSS 中如何正确使用 box-shadow 设置透明度阴影?

    css 中覆盖默认 box-shadow 样式时的报错问题 在尝试修改导航栏阴影时遇到报错,分析发现是 box-shadow 样式引起的问题。 问题原因 使用 !important 仍无法覆盖默认样式的原因在于,你使用了 rgb() 而不是 rgba(),这会导致语法错误。 立即学习“前端免费学习笔…

    2025年12月24日
    300
  • 为何scss中嵌套使用/*rtl:ignore*/无法被postcss-rtl插件识别?

    postcss-rtl插件为何不支持在scss中嵌套使用/*rtl:ignore*/ 在使用postcss-rtl插件时,如果希望对某个样式不进行转换,可以使用/*rtl:ignore*/在选择器前面进行声明。然而,当样式文件为scss格式时,该声明可能会失效,而写在css文件中则有效。 原因 po…

    2025年12月24日
    000
  • CSS 砌体 Catness

    css 就像技术中的其他东西一样 – 它总是在变化和发展。该领域正在进行的开发是 css 网格布局模块级别 3,也称为 css masonry 布局。 theo 制作了一段视频,介绍了它的开发方式以及苹果和谷歌就如何实施它进行的辩论。 所有这些让我很高兴尝试 css 砌体! webkit…

    好文分享 2025年12月24日
    000
  • Sass 中使用 rgba(var –color) 时的透明度问题如何解决?

    rgba(var –color)在 Sass 中无效的解决方法 在 Sass 中使用 rgba(var –color) 时遇到透明问题,可能是因为以下原因: 编译后的 CSS 代码 rgba($themeColor, 0.8) 在编译后会变为 rgba(var(–…

    2025年12月24日
    000
  • ## PostCSS vs. Sass/Less/Stylus:如何选择合适的 CSS 代码编译工具?

    PostCSS 与 Sass/Less/Stylus:CSS 代码编译转换中的异同 在 CSS 代码的编译转换领域,PostCSS 与 Sass/Less/Stylus 扮演着重要的角色,但它们的作用却存在细微差异。 区别 PostCSS 主要是一种 CSS 后处理器,它在 CSS 代码编译后进行处…

    2025年12月24日
    000
  • SCSS 简介:增强您的 CSS 工作流程

    在 web 开发中,当项目变得越来越复杂时,编写 css 可能会变得重复且具有挑战性。这就是 scss (sassy css) 的用武之地,它是一个强大的 css 预处理器。scss 带来了变量、嵌套、混合等功能,使开发人员能够编写更干净、更易于维护的代码。在这篇文章中,我们将深入探讨 scss 是…

    2025年12月24日
    000
  • 在 Sass 中使用 Mixin

    如果您正在深入研究前端开发世界,那么您很可能遇到过sass(语法很棒的样式表)。 sass 是一个强大的 css 预处理器,它通过提供变量、嵌套、函数和 mixins 等功能来增强您的 css 工作流程。在这些功能中,mixins 作为游戏规则改变者脱颖而出,允许您有效地重用代码并保持样式表的一致性…

    2025年12月24日
    200
  • SCSS:创建模块化 CSS

    介绍 近年来,css 预处理器的使用在 web 开发人员中显着增加。 scss (sassy css) 就是这样一种预处理器,它允许开发人员编写模块化且可维护的 css 代码。 scss 是 css 的扩展,添加了更多特性和功能,使其成为设计网站样式的强大工具。在本文中,我们将深入探讨使用 scss…

    2025年12月24日
    000
  • SCSS – 增强您的 CSS 工作流程

    在本文中,我们将探索 scss (sassy css),这是一个 css 预处理器,它通过允许变量、嵌套规则、mixins、函数等来扩展 css 的功能。 scss 使 css 的编写和维护变得更加容易,尤其是对于大型项目。 1.什么是scss? scss 是 sass(syntropically …

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信