Mergulho Profundo do Mirage JS: Entendendo Fábricas, Acessórios e Serializadores (Parte 2)
Publicados: 2022-03-10No artigo anterior desta série, estudamos Modelos e Associações no que se refere ao Mirage. Expliquei que os modelos nos permitem criar dados simulados dinâmicos que o Mirage serviria para nosso aplicativo quando fizer uma solicitação para nossos terminais simulados. Neste artigo, veremos três outros recursos do Mirage que permitem simulações de API ainda mais rápidas. Vamos mergulhar direto!
Nota : Eu recomendo a leitura dos meus dois primeiros artigos se você não tiver uma visão realmente sólida do que será discutido aqui. No entanto, você ainda pode acompanhar e fazer referência aos artigos anteriores quando necessário.
- Configurando API Mocking com Mirage JS e Vue
- Modelos e associações do Mirage JS
Fábricas
Em um artigo anterior, expliquei como o Mirage JS é usado para simular a API de back-end, agora vamos supor que estamos simulando um recurso de produto no Mirage. Para conseguir isso, criaríamos um manipulador de rotas que será responsável por interceptar solicitações para um determinado endpoint e, nesse caso, o endpoint é api/products
. O manipulador de rota que criamos retornará todos os produtos. Abaixo está o código para conseguir isso no Mirage:
import { Server, Model } from 'miragejs'; new Server({ models: { product: Model, }, routes() { this.namespace = "api"; this.get('products', (schema, request) => { return schema.products.all() }) } }); },
A saída acima seria:
{ "products": [] }
Vemos na saída acima que o recurso do produto está vazio. No entanto, isso é esperado, pois ainda não criamos nenhum registro.
Dica profissional : Mirage fornece abreviação necessária para endpoints de API convencionais. Portanto, o manipulador de rotas acima também pode ser tão curto quanto: this.get('/products')
.
Vamos criar registros do modelo do product
a serem armazenados no banco de dados Mirage usando o método seeds
em nossa instância do Server
:
seeds(server) { server.create('product', { name: 'Gemini Jacket' }) server.create('product', { name: 'Hansel Jeans' }) },
A saída:
{ "products": [ { "name": "Gemini Jacket", "id": "1" }, { "name": "Hansel Jeans", "id": "2" } ] }
Como você pode ver acima, quando nosso aplicativo frontend fizer uma solicitação para /api/products
, ele receberá de volta uma coleção de produtos conforme definido no método seeds
.
Usar o método seeds
para semear o banco de dados do Mirage é um passo em relação à necessidade de criar manualmente cada entrada como um objeto. No entanto, não seria prático criar 1.000 (ou um milhão) novos registros de produtos usando o padrão acima. Daí a necessidade de fábricas .
Explicação das Fábricas
As fábricas são uma maneira mais rápida de criar novos registros de banco de dados. Eles nos permitem criar rapidamente vários registros de um determinado modelo com variações a serem armazenadas no banco de dados Mirage JS.
As fábricas também são objetos que facilitam a geração de dados com aparência realista sem a necessidade de semear esses dados individualmente. Fábricas são mais receitas ou plantas para criar registros de modelos.
Criando uma fábrica
Vamos examinar uma Fábrica criando uma. A fábrica que criaríamos será usada como um modelo para a criação de novos produtos em nosso banco de dados 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({}) } })
Do acima, você veria que adicionamos uma propriedade factories
à nossa instância do Server
e definimos outra propriedade dentro dela que, por convenção, tem o mesmo nome do modelo para o qual queremos criar uma fábrica, neste caso, esse modelo é o modelo de product
. O trecho acima descreve o padrão que você seguiria ao criar fábricas no Mirage JS.
Embora tenhamos uma fábrica para o modelo do product
, não adicionamos propriedades a ele. As propriedades de uma fábrica podem ser tipos simples como strings , booleanos ou números , ou funções que retornam dados dinâmicos como veríamos na implementação completa de nossa nova fábrica de produtos abaixo:
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; }, }), }, })
No trecho de código acima, estamos especificando alguma lógica javascript via Math.random
para criar dados dinâmicos toda vez que a fábrica é usada para criar um novo registro de produto. Isso mostra a força e flexibilidade das Fábricas.
Vamos criar um produto utilizando a fábrica que definimos acima. Para fazer isso, chamamos server.create
e passamos o nome do modelo ( product
) como uma string. O Mirage criará um novo registro de um produto usando a fábrica de produtos que definimos. O código que você precisa para fazer isso é o seguinte:
new Server({ seeds(server) { server.create("product") } })
Dica profissional : você pode executar console.log(server.db.dump())
para ver os registros no banco de dados do Mirage.
Um novo registro semelhante ao abaixo foi criado e armazenado no banco de dados do Mirage.
{ "products": [ { "rating": 3, "category": "Computing", "price": "$739", "name": "Awesome Product 0", "id": "1" } ] }
Substituindo fábricas
Podemos substituir alguns ou mais valores fornecidos por uma fábrica passando-os explicitamente da seguinte forma:
server.create("product", {name: "Yet Another Product", rating: 5, category: "Fashion" })
O registro resultante seria semelhante a:
{ "products": [ { "rating": 5, "category": "Fashion", "price": "$782", "name": "Yet Another Product", "id": "1" } ] }
Criar lista
Com uma fábrica instalada, podemos usar outro método no objeto servidor chamado createList
. Este método permite a criação de vários registros de um modelo específico, passando o nome do modelo e o número de registros que você deseja criar. Abaixo está seu uso:
server.createList("product", 10)
Ou
server.createList("product", 1000)
Como você observará, o método createList
acima recebe dois argumentos: o nome do modelo como uma string e um inteiro positivo diferente de zero representando o número de registros a serem criados. Então, a partir do exposto, acabamos de criar 500 registros de produtos! Esse padrão é útil para testes de interface do usuário, como você verá em um artigo futuro desta série.
Luminárias
No teste de software, um acessório ou acessório de teste é um estado de um conjunto ou coleção de objetos que servem como linha de base para a execução de testes. O principal objetivo de um acessório é garantir que o ambiente de teste seja bem conhecido para tornar os resultados repetíveis.
Mirage permite que você crie fixtures e use-os para semear seu banco de dados com dados iniciais.
Nota : É recomendado que você use as fábricas 9 em cada 10 vezes, pois elas tornam seus mocks mais fáceis de manter.
Criando uma luminária
Vamos criar um fixture simples para carregar dados em nosso banco de dados:
fixtures: { products: [ { id: 1, name: 'T-shirts' }, { id: 2, name: 'Work Jeans' }, ], },
Os dados acima são carregados automaticamente no banco de dados como dados iniciais do Mirage. No entanto, se você tiver uma função de sementes definida, o Mirage ignoraria seu acessório com as suposições de que você pretendia que ele fosse substituído e, em vez disso, usaria fábricas para propagar seus dados.
Luminárias em conjunto com fábricas
A Mirage prevê que você use as Luminárias junto com as Fábricas. Você pode conseguir isso chamando server.loadFixtures()
. Por exemplo:
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") },
Arquivos de acessórios
Idealmente, você gostaria de criar seus equipamentos em um arquivo separado do server.js
e importá-lo. Por exemplo, você pode criar um diretório chamado fixtures
e nele criar products.js
. Em products.js
adicione:
// <PROJECT-ROOT>/fixtures/products.js export default [ { id: 1, name: 'iPhone 7' }, { id: 2, name: 'Smart TV' }, { id: 3, name: 'Pressing Iron' }, ];
Em seguida, em server.js
, importe e use o acessório de produtos da seguinte forma:
import products from './fixtures/products'; fixtures: { products, },
Estou usando a abreviação da propriedade ES6 para atribuir o array products importado à propriedade products
do objeto fixtures.
É digno de menção que os fixtures seriam ignorados pelo Mirage JS durante os testes, exceto que você dissesse explicitamente para não usar server.loadFixtures()
Fábricas vs. Luminárias
Na minha opinião, você deve se abster de usar fixtures, exceto que você tem um caso de uso específico onde eles são mais adequados do que fábricas. As luminárias tendem a ser mais detalhadas, enquanto as fábricas são mais rápidas e envolvem menos teclas.
Serializadores
É importante retornar uma carga JSON esperada para o front-end, portanto, serializadores .
Um serializador é um objeto responsável por transformar um **Model** ou **Collection** retornado de seus manipuladores de rota em uma carga JSON formatada da maneira que seu aplicativo de front-end espera.
Documentos Mirage
Vamos pegar este manipulador de rota por exemplo:
this.get('products/:id', (schema, request) => { return schema.products.find(request.params.id); });
Um serializador é responsável por transformar a resposta em algo assim:
{ "product": { "rating": 0, "category": "Baby Products", "price": "$654", "name": "Awesome Product 1", "id": "2" } }
Serializadores integrados do Mirage JS
Para trabalhar com serializadores Mirage JS, você teria que escolher com qual serializador interno começar. Essa decisão seria influenciada pelo tipo de JSON que seu back-end enviaria para seu aplicativo de front-end. Mirage vem incluído com os seguintes serializadores:
-
JSONAPISerializer
Este serializador segue a especificação JSON:API. -
ActiveModelSerializer
Este serializador destina-se a imitar APIs que se assemelham a APIs Rails construídas com a gem active_model_serializer. -
RestSerializer
ORestSerializer
é um serializador Mirage JS “catch all” para outras APIs comuns.
Definição do serializador
Para definir um serialize, importe o serializador apropriado, por exemplo RestSerializer
de miragejs
assim:
import { Server, RestSerializer } from "miragejs"
Em seguida, na instância do Server
:
new Server({ serializers: { application: RestSerializer, }, })
O RestSerializer
é usado pelo Mirage JS por padrão. Portanto, é redundante configurá-lo explicitamente. O trecho acima é para fins exemplificativos.
Vamos ver a saída de JSONAPISerializer
e ActiveModelSerializer
no mesmo manipulador de rotas conforme definimos acima
JSONAPISerializer
import { Server, JSONAPISerializer } from "miragejs" new Server({ serializers: { application: JSONAPISerializer, }, })
A saída:
{ "data": { "type": "products", "id": "2", "attributes": { "rating": 3, "category": "Electronics", "price": "$1711", "name": "Awesome Product 1" } } }
ActiveModelSerializer
Para ver o ActiveModelSerializer funcionando, eu modificaria a declaração de category
na fábrica de produtos para:
productCategory() { let categories = [ 'Electronics', 'Computing', 'Fashion', 'Gaming', 'Baby Products', ]; let randomCategoryIndex = Math.floor( Math.random() * categories.length ); let randomCategory = categories[randomCategoryIndex]; return randomCategory; },
Tudo o que fiz foi alterar o nome da propriedade para productCategory
para mostrar como o serializador lidaria com isso.
Em seguida, definimos o serializador ActiveModelSerializer
assim:
import { Server, ActiveModelSerializer } from "miragejs" new Server({ serializers: { application: ActiveModelSerializer, }, })
O serializador transforma o JSON retornado como:
{ "rating": 2, "product_category": "Computing", "price": "$64", "name": "Awesome Product 4", "id": "5" }
Você notará que productCategory
foi transformado em product_category
que está em conformidade com a gem active_model_serializer do ecossistema Ruby.
Personalizando serializadores
O Mirage fornece a capacidade de personalizar um serializador. Digamos que seu aplicativo exija que seus nomes de atributos sejam camelcases, você pode substituir RestSerializer
para conseguir isso. Estaríamos utilizando a biblioteca de utilitários lodash
:
import { RestSerializer } from 'miragejs'; import { camelCase, upperFirst } from 'lodash'; serializers: { application: RestSerializer.extend({ keyForAttribute(attr) { return upperFirst(camelCase(attr)); }, }), },
Isso deve produzir JSON da forma:
{ "Rating": 5, "ProductCategory": "Fashion", "Price": "$1386", "Name": "Awesome Product 4", "Id": "5" }
Empacotando
Você conseguiu! Espero que você tenha uma compreensão mais profunda do Mirage por meio deste artigo e também tenha visto como a utilização de fábricas, acessórios e serializadores permitiria que você criasse mais simulações de API semelhantes à produção com o Mirage.
- Parte 1: Entendendo os modelos e associações do Mirage JS
- Parte 2: Noções básicas sobre fábricas, acessórios e serializadores
- Parte 3: Entendendo o Tempo, Resposta e Transmissão
- Parte 4: Usando Mirage JS e Cypress para testes de interface do usuário