Mirage JS 深入探讨:了解工厂、夹具和序列化程序(第 2 部分)

已发表: 2022-03-10
快速总结 ↬在 Mirage JS Deep Dive 系列的第二部分中,我们将了解 Mirage JS 的工厂、夹具和序列化程序。 我们将看到他们如何使用 Mirage 实现快速 API 模拟。

在本系列的上一篇文章中,我们了解了与 Mirage 相关的模型和关联。 我解释说,模型允许我们创建动态模拟数据,当 Mirage 向我们的模拟端点发出请求时,它将提供给我们的应用程序。 在本文中,我们将介绍 Mirage 的其他三个功能,这些功能可以实现更快速的 API 模拟。 让我们潜入水中!

注意我强烈建议您阅读我的前两篇文章,如果您还没有真正掌握这里将要讨论的内容。 但是,您仍然可以在必要时继续跟进并参考以前的文章。

  • 使用 Mirage JS 和 Vue 设置 API 模拟
  • Mirage JS 模型和关联

工厂

在之前的文章中,我解释了如何使用 Mirage JS 模拟后端 API,现在假设我们正在模拟 Mirage 中的产品资源。 为此,我们将创建一个路由处理程序,该处理程序将负责拦截对特定端点的请求,在这种情况下,端点是api/products 。 我们创建的路由处理程序将返回所有产品。 下面是在 Mirage 中实现此目的的代码:

 import { Server, Model } from 'miragejs'; new Server({ models: { product: Model, }, routes() { this.namespace = "api"; this.get('products', (schema, request) => { return schema.products.all() }) } }); },

上面的输出将是:

 { "products": [] }

我们从上面的输出中看到产品资源是空的。 然而,这是意料之中的,因为我们还没有创建任何记录。

专业提示Mirage 提供了传统 API 端点所需的简写。 所以上面的路由处理程序也可以短如: this.get('/products')

跳跃后更多! 继续往下看↓

让我们使用Server实例上的seeds方法创建要存储在 Mirage 数据库中的product模型记录:

 seeds(server) { server.create('product', { name: 'Gemini Jacket' }) server.create('product', { name: 'Hansel Jeans' }) },

输出:

 { "products": [ { "name": "Gemini Jacket", "id": "1" }, { "name": "Hansel Jeans", "id": "2" } ] }

正如您在上面看到的,当我们的前端应用程序向/api/products发出请求时,它将返回seeds方法中定义的产品集合。

使用seeds方法播种 Mirage 的数据库是一个步骤,无需手动将每个条目创建为对象。 但是,使用上述模式创建 1000 条(或 100 万条)新产品记录是不切实际的。 因此需要工厂

工厂解释

工厂是创建新数据库记录的更快方法。 它们使我们能够快速创建特定模型的多条记录,并将变体存储在 Mirage JS 数据库中。

工厂也是可以轻松生成逼真数据的对象,而无需单独播种这些数据。 工厂更多是用于创建模型记录的配方蓝图

创建工厂

让我们通过创建一个工厂来检查一下。 我们将创建的工厂将用作在 Mirage JS 数据库中创建新产品的蓝图。

 import { Factory } from 'miragejs' new Server({ // including the model definition for a better understanding of what's going on models: { product: Model }, factories: { product: Factory.extend({}) } })

从上面,您会看到我们向我们的Server实例添加了一个factories属性,并在其中定义了另一个属性,按照惯例,该属性与我们要为其创建工厂的模型同名,在这种情况下,该模型是product型号。 上面的代码片段描述了在 Mirage JS 中创建工厂时将遵循的模式。

虽然我们有一个product模型的工厂,但我们真的没有给它添加属性。 工厂的属性可以是简单的类型,如字符串布尔值数字,也可以是返回动态数据的函数,如下面的新产品工厂的完整实现所示:

 import { Server, Model, Factory } from 'miragejs' new Server({ models: { product: Model }, factories: { product: Factory.extend({ name(i) { // i is the index of the record which will be auto incremented by Mirage JS return `Awesome Product ${i}`; // Awesome Product 1, Awesome Product 2, etc. }, price() { let minPrice = 20; let maxPrice = 2000; let randomPrice = Math.floor(Math.random() * (maxPrice - minPrice + 1)) + minPrice; return `$ ${randomPrice}`; }, category() { let categories = [ 'Electronics', 'Computing', 'Fashion', 'Gaming', 'Baby Products', ]; let randomCategoryIndex = Math.floor( Math.random() * categories.length ); let randomCategory = categories[randomCategoryIndex]; return randomCategory; }, rating() { let minRating = 0 let maxRating = 5 return Math.floor(Math.random() * (maxRating - minRating + 1)) + minRating; }, }), }, })

在上面的代码片段中,我们通过Math.random指定一些 javascript 逻辑,以在每次使用工厂创建新产品记录时创建动态数据。 这显示了工厂的实力和灵活性。

让我们利用上面定义的工厂创建一个产品。 为此,我们调用server.create并将模型名称 ( product ) 作为字符串传入。 然后,Mirage 将使用我们定义的产品工厂创建产品的新记录。 为此,您需要的代码如下:

 new Server({ seeds(server) { server.create("product") } })

专业提示您可以运行console.log(server.db.dump())来查看 Mirage 数据库中的记录。

在 Mirage 数据库中创建并存储了一条类似于以下的新记录。

 { "products": [ { "rating": 3, "category": "Computing", "price": "$739", "name": "Awesome Product 0", "id": "1" } ] }

覆盖工厂

我们可以通过显式传递工厂提供的一些或更多值来覆盖它们,如下所示:

 server.create("product", {name: "Yet Another Product", rating: 5, category: "Fashion" })

结果记录将类似于:

 { "products": [ { "rating": 5, "category": "Fashion", "price": "$782", "name": "Yet Another Product", "id": "1" } ] }

创建列表

有了工厂,我们可以在服务器对象上使用另一种方法createList 。 此方法允许通过传入模型名称和要创建的记录数来创建特定模型的多个记录。 下面是它的用法:

 server.createList("product", 10)

要么

server.createList("product", 1000)

正如您将看到的,上面的createList方法有两个参数:模型名称作为字符串和一个非零正整数,表示要创建的记录数。 所以从上面,我们刚刚创建了 500 条产品记录! 此模式对于 UI 测试很有用,您将在本系列的后续文章中看到。

夹具

在软件测试中,测试夹具夹具是一组或一组对象的状态,用作运行测试的基线。 夹具的主要目的是确保测试环境是众所周知的,以使结果可重复。

Mirage 允许您创建固定装置并使用它们为您的数据库播种初始数据。

注意建议您使用工厂 10 次中的 9 次,因为它们使您的模拟更易于维护。

创建夹具

让我们创建一个简单的夹具来将数据加载到我们的数据库中:

 fixtures: { products: [ { id: 1, name: 'T-shirts' }, { id: 2, name: 'Work Jeans' }, ], },

以上数据作为 Mirage 的初始数据自动加载到数据库中。 但是,如果您定义了种子函数,Mirage 将忽略您的固定装置,并假设您打算覆盖它,而是使用工厂来为您的数据播种。

与工厂结合的固定装置

Mirage 为您提供了在 Factory 旁边使用 Fixtures 的条件。 您可以通过调用server.loadFixtures()来实现。 例如:

 fixtures: { products: [ { id: 1, name: "iPhone 7" }, { id: 2, name: "Smart TV" }, { id: 3, name: "Pressing Iron" }, ], }, seeds(server) { // Permits both fixtures and factories to live side by side server.loadFixtures() server.create("product") },

夹具文件

理想情况下,您希望在server.js的单独文件中创建您的固定装置并导入它。 例如,您可以创建一个名为fixtures的目录并在其中创建products.js 。 在products.js添加:

 // <PROJECT-ROOT>/fixtures/products.js export default [ { id: 1, name: 'iPhone 7' }, { id: 2, name: 'Smart TV' }, { id: 3, name: 'Pressing Iron' }, ];

然后在server.js中导入并使用 products 夹具,如下所示:

 import products from './fixtures/products'; fixtures: { products, },

我正在使用 ES6 属性简写,以便将导入的 products 数组分配给 fixtures 对象的products属性。

值得一提的是,Mirage JS 在测试期间会忽略固定装置,除非您明确告诉它不要使用server.loadFixtures()

工厂与固定装置

在我看来,你应该避免使用夹具,除非你有一个比工厂更适合的特定用例。 固定装置往往更冗长,而工厂更快并且涉及更少的击键。

序列化器

返回预期到前端的 JSON 有效负载非常重要,因此是serializers

序列化程序是一个对象,负责将路由处理程序返回的 **Model** 或 **Collection** 转换为按照前端应用程序期望的方式格式化的 JSON 有效负载。

幻影文档

让我们以这个路由处理程序为例:

 this.get('products/:id', (schema, request) => { return schema.products.find(request.params.id); });

序列化器负责将响应转换为如下内容:

 { "product": { "rating": 0, "category": "Baby Products", "price": "$654", "name": "Awesome Product 1", "id": "2" } }

Mirage JS 内置序列化程序

要使用 Mirage JS 序列化程序,您必须选择从哪个内置序列化程序开始。 这个决定会受到后端最终发送到前端应用程序的 JSON 类型的影响。 Mirage 包含在以下序列化程序中:

  • JSONAPISerializer
    此序列化程序遵循 JSON:API 规范。
  • ActiveModelSerializer
    该序列化程序旨在模仿类似于使用 active_model_serializer gem 构建的 Rails API 的 API。
  • RestSerializer
    RestSerializer是 Mirage JS 用于其他常见 API 的“包罗万象”序列化程序。

序列化器定义

要定义序列化,请从miragejs导入适当的序列化程序,例如RestSerializer ,如下所示:

 import { Server, RestSerializer } from "miragejs"

然后在Server实例中:

 new Server({ serializers: { application: RestSerializer, }, })

Mirage JS 默认使用RestSerializer 。 所以显式设置它是多余的。 上面的代码片段用于示例目的。

让我们在上面定义的同一路由处理程序上查看JSONAPISerializerActiveModelSerializer的输出

JSONAPISerializer

 import { Server, JSONAPISerializer } from "miragejs" new Server({ serializers: { application: JSONAPISerializer, }, })

输出:

 { "data": { "type": "products", "id": "2", "attributes": { "rating": 3, "category": "Electronics", "price": "$1711", "name": "Awesome Product 1" } } }

ActiveModelSerializer

要查看 ActiveModelSerializer 的工作情况,我会将产品工厂中的category声明修改为:

 productCategory() { let categories = [ 'Electronics', 'Computing', 'Fashion', 'Gaming', 'Baby Products', ]; let randomCategoryIndex = Math.floor( Math.random() * categories.length ); let randomCategory = categories[randomCategoryIndex]; return randomCategory; },

我所做的只是将属性的名称更改为productCategory以显示序列化程序将如何处理它。

然后,我们像这样定义ActiveModelSerializer序列化器:

 import { Server, ActiveModelSerializer } from "miragejs" new Server({ serializers: { application: ActiveModelSerializer, }, })

序列化程序将返回的 JSON 转换为:

 { "rating": 2, "product_category": "Computing", "price": "$64", "name": "Awesome Product 4", "id": "5" }

您会注意到productCategory已转换为product_category ,它符合 Ruby 生态系统的 active_model_serializer gem。

自定义序列化器

Mirage 提供了自定义序列化程序的能力。 假设您的应用程序要求您的属性名称为驼峰式,您可以覆盖RestSerializer来实现这一点。 我们将使用lodash实用程序库:

 import { RestSerializer } from 'miragejs'; import { camelCase, upperFirst } from 'lodash'; serializers: { application: RestSerializer.extend({ keyForAttribute(attr) { return upperFirst(camelCase(attr)); }, }), },

这应该产生以下形式的 JSON:

 { "Rating": 5, "ProductCategory": "Fashion", "Price": "$1386", "Name": "Awesome Product 4", "Id": "5" }

包起来

你做到了! 希望您通过本文对 Mirage 有了更深入的了解,并且您还了解了如何利用工厂、固定装置和序列化程序使您能够使用 Mirage 创建更多类似于生产的 API 模拟。

  • 第 1 部分:了解 Mirage JS 模型和关联
  • 第 2 部分:了解工厂、夹具和序列化程序
  • 第 3 部分:了解时序、响应和直通
  • 第 4 部分:使用 Mirage JS 和 Cypress 进行 UI 测试