草庐IT

如何优雅在webpack项目实现mock服务器

孤猎88 2023-03-28 原文

为什么需要 mock

至于平时开发为什么需要 mock 数据,应该大多数的同学都非常清楚了;如果前后端同步开发的话,少不了这一步,在需求评审,技术评估等流程通过后,前后端就会约定接口 api 的字段(但是在部分公司可能会少了这一步),确认接口 api 字段约定之后,前端就可以通过 mock server 去 mock 数据进行开发了,不需要等后端开发完 api 接口再去对接,但是有些同学在开发的过程中经常已经把 ui 弄好了,就在苦苦等后端大哥的接口...白白浪费了不必要的时间,如果可以自己 mock 数据开发,那等后端接口都好了只需要把域名或者接口前缀换一下再联调一下就万事大吉了。

mock 数据的方式

json schema

有些同学喜欢在代码里面用 json schema 的形式去 mock 数据,比如:

export function xhr(params = {}) { if (process.env.NODE_ENV === "development" && useMock) { return delay(2000).then(() => Promise.resolve(require("./mock/list.json"))); } return request({ url: "xxxxx", method: "POST", }); } function delay(ms = 2000) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, ms); }); } 优点:简单。

缺点:第一,需要侵入逻辑代码里面;第二,没办法真正的模拟 ajax 请求,因为这种 mock 是不会发起 http 请求的;第三,不能获取传递的参数去做对应的事情;第四,不能修改 http 的状态;第五,打包项目的时候还会被打包进 bundle。

结论:缺点明显大于优点。

可视化的 mock

市面上有很多可视化的 mock api 方案,比如 apiFox。

优点:ApiFox 集成了 Postman + Swagger + Mock + JMeter,是一款做得比较的好的可视化 mock 解决方案。

缺点:如果只有前端团队在单独使用,就有点大材小用没必要,如果是前后端测试都同时在使用的话,那就是一个不错的选择。

结论:ApiFox 更像一个团队协作的 mock 工具。

在 webpack 实现 mock server 需要的知识

如果要自己在 webpack 项目的搭建一个定制化的 mock server 需要如下的知识点。

  • 一点点的 webpack 知识
  • 一点点的 node 知识
很简单的啦!

实现 webpack mock server

小试牛刀

在 webpack 中实现定制化的 mock server ,需要借助 webpack-dev-server,也就是 webpack 配置下 devServer 字段。该字段下提供了一个onBeforeSetupMiddleware的一个钩子,回调参数里面为我们提供了一个app参数,参数是一个 node 的服务。

既然知道了app是一个 node 服务,那让我们小试牛刀一下(很快不疼,一下就过去了 ?)。

// webpack.config.js module.exports = { devServer: { onBeforeSetupMiddleware:(server){ server.app.get('/api/list',(req,res,next){ res.json({ code:1, data:{ name:"孤猎" } }) }) } }, }; // 随便一个js定义一个ajax请求 let getData = ()=>{ fetch('/api/list').then(res=>res.json()).then(res=>{ console.log(res.data) }) } 这里就实现了一个简单的 mock server 的了,是不是很简单。

但是这是有缺点的,总不能没定义一个 api 都在 onBeforeSetupMiddleware 的 server.app.xxx 吧,这得多麻烦,让我们稍微修改一下代码,大概用个小 50 行代码就好。

大刀阔斧

确认需要实现一下怎么的 mock server:

  • 只读取跟目录的 mock 文件夹下的 js 文件

  • js 文件 mock 数据需要通过module.exports={}导出

  • mock server 可以修改各种状态,支持 GET,POST 等请求

  • 支持延时

  • 期望的使用方式:module.exports = {"GET /api/list":(req,res)=>{},"POST /api/list":{},"GET /api/list 3000":(req,res)=>{} },可以是函数也可以是 json,3000 是延时时间,请求 api 分三段请求方法 路径 延时

首先,先把刚才的 get 请求改成 use,让所以的请求都打进来,打进来后不管三七二十一先调 next;然后加个判断,只处理包含 api/mock 的请求,其他的让它改干嘛就干嘛。

具体的实现就直接贴代码了,具体的看注释就好,在注释里详细解释。

具体实现就抽离到一个单独的 js 文件实现,具体哪个文件看个人喜欢了。

const fs = require("fs"); const path = require("path"); module.exports = function () { // 这里收mock数据的根目录,我们只认这目录下文件 let mockDataPath = path.resolve(__dirname, "../mock/"); // 判断根目录是否存在mock目录 let existsMockDir = fs.existsSync(mockDataPath); // 获取mock目录下的所有文件的mock数据 let getMockData = () => { // 如果mock目录存在就走if逻辑 if (existsMockDir) { /** * 通过readdirSync获取mock目录下的所有文件名称 * 再通过require取出数据 */ let modules = fs.readdirSync(mockDataPath); return modules.reduce((pre, module) => { return { ...pre, ...require(path.join(mockDataPath, "./" + module)), }; }, {}); } else { console.log("根目录不存在mock文件夹,请创建一个根目录创建一个mock文件夹"); return {}; } }; // 该函数负责重新处理请求的路径 let splitApiPath = (mockData) => { let data = {}; for (let path in mockData) { let [method, apiPath, sleep] = path.split(" "); let newApiPath = method.toLocaleUpperCase() + apiPath; data[newApiPath] = { path: newApiPath, method, sleep, callback: mockData[path], }; } return data; }; // 该函数是一个延时函数 let delayFn = (sleep) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, sleep); }); }; // 最后返回一个函数 return async (req, res, next) => { let { baseUrl, method } = req; // 只处理请求路径包含api的请求 if (baseUrl.indexOf("api") === -1 || !existsMockDir) { return next(); } let mockData = splitApiPath(getMockData()); let path = method.toLocaleUpperCase() + baseUrl; let { sleep, callback } = mockData[path]; let isFuntion = callback.__proto__ === Function.prototype; // 如果mock api 有延时存在 if (sleep && sleep > 0) { await delayFn(sleep); } // 如果mock api 的值是一个函数 if (isFuntion) { callback(req, res); } else { // 如果mock api 的值是一个json res.json({ ...callback, }); } next(); }; }; 在 webpack.config.js 引入刚刚实现的方法

module.exports = { devServer: { onBeforeSetupMiddleware:(server){ server.app.use('*',mockServer()) } }, }; 使用

在项目根目录定义一个 mock 目录,随便创一个 js 文件。

module.exports = { "GET /api/list": { code: 0, data: { list: [ { name: "syf", age: 18, }, ], }, }, "POST /api/list 3000": (req, res) => { res.json({ code: 0, data: { list: [ { name: "gulie", age: 19, }, ], }, }); }, }; 到此为止,期望的定制化的 mock serve 就大功告成了。

后话

如果各位同学还有更高的追求的话,可以在此基础上继续定制化自己的需求,还有在根目录的 mock 所有数据不会被打包进 bundle,也没有侵入到逻辑代码里面。

如果各位同学对 vite 项目怎么实现 mock server 感兴趣的话,可以留言或者私信,需求多的话,会出一篇在 vite 项目的 mock server。

谢谢大家的阅读 ?。

有关如何优雅在webpack项目实现mock服务器的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  10. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

随机推荐