Mirage JS Deep Dive: Zrozumienie fabryk, urządzeń i serializatorów (część 2)

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ W drugiej części serii Mirage JS Deep Dive przyjrzymy się fabrykom, urządzeniom i serializatorom Mirage JS. Zobaczymy, jak umożliwiają szybkie mockowanie API za pomocą Mirage.

W poprzednim artykule z tej serii przestudiowaliśmy modele i skojarzenia w odniesieniu do Mirage. Wyjaśniłem, że Modele pozwalają nam na tworzenie dynamicznych atrap danych, które Mirage podałby do naszej aplikacji, gdy wysyła żądanie do naszych atrap. W tym artykule przyjrzymy się trzem innym funkcjom Mirage, które pozwalają na jeszcze szybsze mockowanie API. Zanurzmy się od razu!

Uwaga : Gorąco polecam przeczytanie moich pierwszych dwóch artykułów, jeśli nie masz naprawdę solidnego zrozumienia tego, co zostałoby tutaj omówione. Możesz jednak nadal śledzić i odwoływać się do poprzednich artykułów, gdy jest to konieczne.

  • Konfigurowanie mockowania API za pomocą Mirage JS i Vue
  • Modele i powiązania Mirage JS

Fabryki

W poprzednim artykule wyjaśniłem, w jaki sposób Mirage JS jest używany do naśladowania API zaplecza, teraz załóżmy, że kpimy z zasobu produktu w Mirage. Aby to osiągnąć, stworzylibyśmy procedurę obsługi tras , która będzie odpowiedzialna za przechwytywanie żądań do określonego punktu końcowego, a w tym przypadku punktem końcowym jest api/products . Utworzony przez nas program obsługi tras zwróci wszystkie produkty. Poniżej znajduje się kod do osiągnięcia tego w Mirage:

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

Wynikiem powyższego byłoby:

 { "products": [] }

Z powyższego wyniku widzimy, że zasób produktu jest pusty. Jest to jednak oczekiwane, ponieważ nie stworzyliśmy jeszcze żadnych rekordów.

Wskazówka dla profesjonalistów: Mirage zapewnia skróty potrzebne dla konwencjonalnych punktów końcowych interfejsu API. Zatem powyższy program obsługi trasy może być tak krótki, jak: this.get('/products') .

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Stwórzmy rekordy modelu product , które mają być przechowywane w bazie danych Mirage przy użyciu metody seeds na naszej instancji Server :

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

Wyjście:

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

Jak widać powyżej, gdy nasza aplikacja frontendowa wyśle ​​żądanie do /api/products , otrzyma z powrotem kolekcję produktów zdefiniowaną w metodzie seeds .

Korzystanie z metody seeds do zaszczepiania bazy danych Mirage to krok od konieczności ręcznego tworzenia każdego wpisu jako obiektu. Jednak nie byłoby praktyczne tworzenie 1000 (lub miliona) nowych rekordów produktów przy użyciu powyższego wzorca. Stąd potrzeba fabryk .

Fabryki wyjaśnione

Fabryki to szybszy sposób tworzenia nowych rekordów w bazie danych. Pozwalają nam na szybkie tworzenie wielu rekordów konkretnego modelu z wariacjami, które mają być przechowywane w bazie danych Mirage JS.

Fabryki to także obiekty, które ułatwiają generowanie realistycznie wyglądających danych bez konieczności umieszczania tych danych pojedynczo. Fabryki to więcej przepisów lub planów tworzenia rekordów z modeli.

Tworzenie fabryki

Przyjrzyjmy się Fabryce, tworząc ją. Stworzona przez nas fabryka posłuży nam jako plan tworzenia nowych produktów w naszej bazie danych 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({}) } })

Z powyższego widać, że dodaliśmy właściwość factories do naszej instancji Server i zdefiniowaliśmy w niej inną właściwość, która zgodnie z konwencją ma taką samą nazwę, jak model, dla którego chcemy utworzyć fabrykę, w tym przypadku modelem tym jest model product . Powyższy fragment przedstawia wzorzec, jakim podążałbyś podczas tworzenia fabryk w Mirage JS.

Chociaż mamy fabrykę modelu product , tak naprawdę nie dodaliśmy do niego właściwości. Właściwościami fabryki mogą być proste typy, takie jak ciągi , wartości logiczne lub liczby , lub funkcje zwracające dane dynamiczne, jak widać w pełnej implementacji naszej nowej fabryki produktów poniżej:

 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; }, }), }, })

W powyższym fragmencie kodu określamy logikę JavaScript za pośrednictwem Math.random , aby tworzyć dynamiczne dane za każdym razem, gdy fabryka jest używana do tworzenia nowego rekordu produktu. To pokazuje siłę i elastyczność fabryk.

Stwórzmy produkt wykorzystujący fabrykę, którą zdefiniowaliśmy powyżej. W tym celu wywołujemy server.create i przekazujemy nazwę modelu ( product ) jako ciąg. Mirage utworzy wtedy nowy rekord produktu przy użyciu zdefiniowanej przez nas fabryki produktów. Kod, którego potrzebujesz, aby to zrobić, to:

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

Wskazówka dla profesjonalistów: Możesz uruchomić console.log(server.db.dump()) , aby zobaczyć rekordy w bazie danych Mirage.

Nowy rekord podobny do poniższego został utworzony i zapisany w bazie danych Mirage.

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

Nadrzędne fabryki

Możemy zastąpić niektóre lub więcej wartości dostarczonych przez fabrykę, jawnie przekazując je w następujący sposób:

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

Wynikowy rekord byłby podobny do:

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

sporządzać listę

Mając fabrykę na miejscu, możemy użyć innej metody na obiekcie serwera o nazwie createList . Metoda ta pozwala na tworzenie wielu rekordów danego modelu poprzez przekazanie nazwy modelu oraz liczby rekordów, które chcesz utworzyć. Poniżej jest to użycie:

 server.createList("product", 10)

Lub

 server.createList("product", 1000)

Jak widać, powyższa metoda createList przyjmuje dwa argumenty: nazwę modelu jako ciąg znaków i niezerową dodatnią liczbę całkowitą reprezentującą liczbę rekordów do utworzenia. Tak więc z powyższego właśnie stworzyliśmy 500 rekordów produktów! Ten wzorzec jest przydatny do testowania interfejsu użytkownika, jak zobaczysz w przyszłym artykule z tej serii.

Oprawy

W testowaniu oprogramowania osprzęt testowy lub osprzęt testowy jest stanem zestawu lub zbioru obiektów, które służą jako punkt odniesienia do przeprowadzania testów. Głównym celem osprzętu jest zapewnienie, że środowisko testowe jest dobrze znane, aby wyniki były powtarzalne.

Mirage umożliwia tworzenie urządzeń i używanie ich do zapełniania bazy danych danymi początkowymi.

Uwaga : Zaleca się używanie fabryk 9 na 10 razy, ponieważ ułatwiają one konserwację makiety.

Tworzenie urządzenia

Stwórzmy proste urządzenie do ładowania danych do naszej bazy danych:

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

Powyższe dane są automatycznie ładowane do bazy danych jako dane początkowe Mirage. Jednakże, jeśli masz zdefiniowaną funkcję seedów, Mirage zignoruje twoje urządzenie z założeniami, że chcesz, aby było to nadpisane, i zamiast tego użyje fabryk do zasiewania twoich danych.

Oprawy w połączeniu z fabrykami

Mirage zapewnia możliwość korzystania z Urządzeń obok fabryk. Możesz to osiągnąć, wywołując server.loadFixtures() . Na przykład:

 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") },

Pliki urządzeń

Idealnie, chciałbyś stworzyć swoje urządzenia w oddzielnym pliku z server.js i zaimportować go. Na przykład możesz stworzyć katalog o nazwie fixtures i w nim stworzyć products.js . W products.js dodaj:

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

Następnie w server.js zaimportuj i użyj urządzenia produktów w następujący sposób:

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

Używam skróconej właściwości ES6 w celu przypisania tablicy products zaimportowanej do właściwości products obiektu Osprzęt.

Warto wspomnieć, że urządzenia zostaną zignorowane przez Mirage JS podczas testów, z wyjątkiem tego, że wyraźnie zabronisz mu tego za pomocą server.loadFixtures()

Fabryki a oprawy

Moim zdaniem powinieneś powstrzymać się od używania urządzeń, chyba że masz szczególny przypadek użycia, w którym są one bardziej odpowiednie niż fabryki. Urządzenia są zwykle bardziej szczegółowe, podczas gdy fabryki są szybsze i wymagają mniej naciśnięć klawiszy.

Serializatory

Ważne jest, aby zwrócić ładunek JSON, który jest oczekiwany do frontendu, stąd serializatory .

Serializator to obiekt, który jest odpowiedzialny za przekształcenie **Model** lub **Kolekcja** zwracanego z programów obsługi tras do ładunku JSON, który jest sformatowany zgodnie z oczekiwaniami Twojej aplikacji frontendowej.

Dokumenty Mirage

Weźmy na przykład ten program obsługi trasy:

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

Serializator jest odpowiedzialny za przekształcenie odpowiedzi na coś takiego:

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

Wbudowane serializatory Mirage JS

Aby pracować z serializatorami Mirage JS, musisz wybrać, od którego wbudowanego serializatora zacząć. Na tę decyzję miałby wpływ typ JSON, który Twój backend ostatecznie wyśle ​​do Twojej aplikacji front-endowej. Mirage jest dostarczany z następującymi serializatorami:

  • JSONAPISerializer
    Ten serializator jest zgodny ze specyfikacją JSON:API.
  • ActiveModelSerializer
    Ten serializator jest przeznaczony do naśladowania interfejsów API, które przypominają interfejsy API Railsów zbudowane za pomocą klejnotu active_model_serializer.
  • RestSerializer
    RestSerializer to serializator Mirage JS „catch all” dla innych popularnych interfejsów API.

Definicja serializatora

Aby zdefiniować serializację, zaimportuj odpowiedni serializator, np RestSerializer z miragejs w następujący sposób:

 import { Server, RestSerializer } from "miragejs"

Następnie w instancji Server :

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

RestSerializer jest domyślnie używany przez Mirage JS. Więc niepotrzebne jest jawne ustawienie tego. Powyższy fragment ma charakter przykładowy.

Zobaczmy dane wyjściowe zarówno JSONAPISerializer , jak i ActiveModelSerializer na tej samej obsłudze trasy, jak zdefiniowaliśmy powyżej

Serializator JSONAPI

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

Wyjście:

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

ActiveModelSerializer

Aby zobaczyć ActiveModelSerializer w pracy, zmodyfikowałbym deklarację category w fabryce produktów na:

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

Wszystko, co zrobiłem, to zmiana nazwy właściwości na productCategory , aby pokazać, jak serializator poradzi sobie z nią.

Następnie definiujemy serializator ActiveModelSerializer w następujący sposób:

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

Serializator przekształca zwrócony JSON jako:

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

Zauważysz, że productCategory został przekształcony w product_category , który jest zgodny z klejnotem active_model_serializer ekosystemu Ruby.

Dostosowywanie serializatorów

Mirage zapewnia możliwość dostosowania serializatora. Załóżmy, że Twoja aplikacja wymaga, aby nazwy atrybutów były ujęte w wielbłądy, możesz przesłonić RestSerializer , aby to osiągnąć. Korzystalibyśmy z biblioteki narzędziowej lodash :

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

Powinno to generować JSON w postaci:

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

Zawijanie

Zrobiłeś to! Mamy nadzieję, że dzięki temu artykułowi lepiej rozumiesz Mirage, a także widziałeś, jak wykorzystanie fabryk, osprzętu i serializatorów umożliwiłoby tworzenie za pomocą Mirage bardziej podobnych do produkcji makiet API.

  • Część 1: Zrozumienie modeli i skojarzeń Mirage JS
  • Część 2: Zrozumienie fabryk, urządzeń i serializatorów
  • Część 3: Zrozumienie czasu, odpowiedzi i przekazywania
  • Część 4: Używanie Mirage JS i Cypress do testowania interfejsu użytkownika