Mergulho Profundo do Mirage JS: Entendendo Fábricas, Acessórios e Serializadores (Parte 2)

Publicados: 2022-03-10
Resumo rápido ↬ Nesta segunda parte da série Mirage JS Deep Dive, veremos as fábricas, luminárias e serializadores da Mirage JS. Veremos como eles permitem simulação rápida de API usando o Mirage.

No 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') .

Mais depois do salto! Continue lendo abaixo ↓

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
    O RestSerializer é 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