Heute haben wir eine lose Kopplung zwischen dem Frontend und dem Backend von Webanwendungen. Sie werden normalerweise von separaten Teams entwickelt, und es ist nicht einfach, diese Teams und die Technologie synchron zu halten. Um einen Teil dieses Problems zu lösen, können wir den API-Server „faken“, den die Backend-Technologie normalerweise erstellen würde, und so entwickeln, als ob die API oder Endpunkte bereits existieren.
Der gebräuchlichste Begriff für die Erstellung simulierter oder „gefakter“ Komponenten ist Mocking. Mocking ermöglicht es Ihnen, die API zu simulieren, ohne (idealerweise) das Frontend zu ändern. Es gibt viele Möglichkeiten, Mocking zu erreichen, und das ist es, was es für die meisten Leute beängstigend macht, zumindest meiner Meinung nach.
Lassen Sie uns behandeln, wie ein gutes API-Mocking aussehen sollte und wie man eine gemockte API in eine neue oder bestehende Anwendung implementiert.
Beachten Sie, dass die Implementierung, die ich gleich zeigen werde, Framework-agnostisch ist – sie kann also mit jedem Framework oder einer reinen JavaScript-Anwendung verwendet werden.
Mirage: Das Mocking-Framework
Der Mocking-Ansatz, den wir verwenden werden, heißt Mirage, der noch relativ neu ist. Ich habe viele Mocking-Frameworks getestet und dieses erst kürzlich entdeckt, und es war ein Game Changer für mich.
Mirage wird als frontend-freundliches Framework vermarktet, das mit einer modernen Benutzeroberfläche ausgestattet ist. Es funktioniert in Ihrem Browser, serverseitig, indem es XMLHttpRequest und Fetch-Anfragen abfängt.
Wir werden eine einfache Anwendung mit einer gemockten API erstellen und dabei einige häufige Probleme behandeln.
Mirage-Setup
Lassen Sie uns eine dieser Standard-To-Do-Anwendungen erstellen, um Mocking zu demonstrieren. Ich werde Vue als mein bevorzugtes Framework verwenden, aber natürlich können Sie auch etwas anderes verwenden, da wir mit einem Framework-agnostischen Ansatz arbeiten.
Installieren Sie also Mirage in Ihrem Projekt
# Using npm
npm i miragejs -D
# Using Yarn
yarn add miragejs -D
Um Mirage nutzen zu können, müssen wir einen „Server“ einrichten (in Anführungszeichen, da es ein Fake-Server ist). Bevor wir mit dem Setup beginnen, behandle ich die Ordnerstruktur, die sich meiner Meinung nach am besten eignet.
/
├── public
├── src
│ ├── api
│ │ └── mock
│ │ ├── fixtures
│ │ │ └── get-tasks.js
│ │ └── index.js
│ └── main.js
├── package.json
└── package-lock.json
Öffnen Sie in einem mock-Verzeichnis eine neue index.js-Datei und definieren Sie Ihren Mock-Server
// api/mock/index.js
import { Server } from 'miragejs';
export default function ({ environment = 'development' } = {}) {
return new Server({
environment,
routes() {
// We will add our routes here
},
});
}
Das Environment-Argument, das wir der Funktionssignatur hinzufügen, ist nur eine Konvention. Wir können bei Bedarf eine andere Umgebung übergeben.
Öffnen Sie nun Ihre App-Bootstrap-Datei. In unserem Fall ist dies die Datei src/main.js, da wir mit Vue arbeiten. Importieren Sie Ihre createServer-Funktion und rufen Sie sie in der Entwicklungsumgebung auf.
// main.js
import createServer from './mock'
if (process.env.NODE_ENV === 'development') {
createServer();
}
Wir verwenden hier die Umgebungsvariable process.env.NODE_ENV, die eine gängige globale Variable ist. Die Bedingung ermöglicht es Mirage, in der Produktion „tree-shaken“ zu werden, daher wird Ihr Produktionsbundle nicht beeinträchtigt.
Das ist alles, was wir für das Setup von Mirage brauchen! Diese Einfachheit macht die DX von Mirage so angenehm.
Unsere createServer-Funktion verwendet standardmäßig die Umgebung development, um diesen Artikel einfach zu halten. In den meisten Fällen wird dies standardmäßig auf test gesetzt, da Sie in den meisten Apps createServer einmal im Entwicklungsmodus, aber viele Male in Testdateien aufrufen werden.
Wie es funktioniert
Bevor wir unsere erste Anfrage stellen, werfen wir einen kurzen Blick darauf, wie Mirage funktioniert.
Mirage ist ein clientseitiges Mocking-Framework, d. h. das gesamte Mocking findet im Browser statt, was Mirage mit der Pretender-Bibliothek tut. Pretender ersetzt vorübergehend native XMLHttpRequest- und Fetch-Konfigurationen, fängt alle Anfragen ab und leitet sie an einen kleinen Schein-Service weiter, an den sich Mirage hängt.
Wenn Sie DevTools öffnen und zum Netzwerk-Tab wechseln, sehen Sie keine Mirage-Anfragen. Das liegt daran, dass die Anfrage von Mirage (über Pretender im Backend) abgefangen und verarbeitet wird. Mirage protokolliert alle Anfragen, was wir gleich behandeln werden.
Lassen Sie uns Anfragen stellen!
Lassen Sie uns eine Anfrage an einen /api/tasks-Endpunkt erstellen, der eine Liste von Aufgaben zurückgibt, die wir in unserer To-Do-App anzeigen werden. Beachten Sie, dass ich axios verwende, um die Daten abzurufen. Das ist nur meine persönliche Präferenz. Auch hier funktioniert Mirage mit nativen XMLHttpRequest, Fetch und jeder anderen Bibliothek.
// components/tasks.vue
export default {
async created() {
try {
const { data } = await axios.get('/api/tasks'); // Fetch the data
this.tasks = data.tasks;
} catch(e) {
console.error(e);
}
}
};
Wenn Sie Ihre JavaScript-Konsole öffnen, sollte dort ein Fehler von Mirage erscheinen
Mirage: Your app tried to GET '/api/tasks', but there was no route defined to handle this request.
Das bedeutet, dass Mirage läuft, aber der Router noch nicht gemockt wurde. Lassen Sie uns das lösen, indem wir diese Route hinzufügen.
Anfragen mocken
In unserer Datei mock/index.js gibt es einen routes()-Hook. Routen-Handler ermöglichen es uns zu definieren, welche URLs vom Mirage-Server behandelt werden sollen.
Um einen Routen-Handler zu definieren, müssen wir ihn innerhalb der routes()-Funktion hinzufügen.
// mock/index.js
export default function ({ environment = 'development' } = {}) {
// ...
routes() {
this.get('/api/tasks', () => ({
tasks: [
{ id: 1, text: "Feed the cat" },
{ id: 2, text: "Wash the dishes" },
//...
],
}))
},
});
}
Der routes()-Hook ist die Art und Weise, wie wir unsere Routen-Handler definieren. Die Verwendung der Methode this.get() ermöglicht es uns, GET-Anfragen zu mocken. Das erste Argument aller Anfragefunktionen ist die URL, die wir behandeln, und das zweite Argument ist eine Funktion, die mit einigen Daten antwortet.
Als Hinweis: Mirage akzeptiert jeden HTTP-Anfragetyp, und jeder Typ hat die gleiche Signatur
this.get('/tasks', (schema, request) => { ... });
this.post('/tasks', (schema, request) => { ... });
this.patch('/tasks/:id', (schema, request) => { ... });
this.put('/tasks/:id', (schema, request) => { ... });
this.del('/tasks/:id', (schema, request) => { ... });
this.options('/tasks', (schema, request) => { ... });
Wir werden die Parameter schema und request der Callback-Funktion in Kürze besprechen.
Damit haben wir unsere Route erfolgreich gemockt und sollten in unserer Konsole eine erfolgreiche Antwort von Mirage sehen.

Arbeiten mit dynamischen Daten
Der Versuch, eine neue To-Do-Aufgabe zu unserer App hinzuzufügen, wird nicht möglich sein, da unsere Daten in der GET-Antwort hartcodierte Werte haben. Mirages Lösung dafür ist, dass sie eine leichtgewichtige Datenschicht bereitstellen, die wie eine Datenbank fungiert. Lassen Sie uns das, was wir bisher haben, korrigieren.
Ähnlich wie beim routes()-Hook definiert Mirage einen seeds()-Hook. Er ermöglicht es uns, initiale Daten für den Server zu erstellen. Ich werde die GET-Daten in den seeds()-Hook verschieben, wo ich sie in die Mirage-Datenbank einfügen werde.
seeds(server) {
server.db.loadData({
tasks: [
{ id: 1, text: "Feed the cat" },
{ id: 2, text: "Wash the dishes" },
],
})
},
Ich habe unsere statischen Daten aus der GET-Methode in den seeds()-Hook verschoben, wo diese Daten in eine Fake-Datenbank geladen werden. Nun müssen wir unsere GET-Methode refaktorisieren, um Daten aus dieser Datenbank zurückzugeben. Das ist eigentlich ziemlich einfach – das erste Argument der Callback-Funktion jeder route()-Methode ist das Schema.
this.get('/api/tasks', (schema) => {
return schema.db.tasks;
})
Jetzt können wir neue To-Do-Elemente zu unserer App hinzufügen, indem wir eine POST-Anfrage stellen
async addTask() {
const { data } = await axios.post('/api/tasks', { data: this.newTask });
this.tasks.push(data);
this.newTask = {};
},
Wir mocken diese Route in Mirage, indem wir einen POST /api/tasks-Routen-Handler erstellen
this.post('/tasks', (schema, request) => {})
Über den zweiten Parameter der Callback-Funktion können wir die gesendete Anfrage sehen.

Innerhalb der requestBody-Eigenschaft befinden sich die von uns gesendeten Daten. Das bedeutet, sie stehen uns nun zur Verfügung, um eine neue Aufgabe zu erstellen.
this.post('/api/tasks', (schema, request) => {
// Take the send data from axios.
const task = JSON.parse(request.requestBody).data
return schema.db.tasks.insert(task)
})
Die id der Aufgabe wird standardmäßig von Mirages Datenbank festgelegt. Daher ist es nicht notwendig, IDs zu verfolgen und sie mit Ihrer Anfrage zu senden – genau wie bei einem echten Server.
Dynamische Routen? Klar!
Das Letzte, was wir behandeln müssen, sind dynamische Routen. Sie ermöglichen uns die Verwendung eines dynamischen Segments in unserer URL, was nützlich zum Löschen oder Aktualisieren eines einzelnen To-Do-Elements in unserer App ist.
Unsere Löschanfrage sollte an /api/tasks/1, /api/tasks/2 und so weiter gehen. Mirage bietet uns eine Möglichkeit, ein dynamisches Segment in der URL zu definieren, wie folgt
this.delete('/api/tasks/:id', (schema, request) => {
// Return the ID from URL.
const id = request.params.id;
return schema.db.tasks.remove(id);
})
Die Verwendung eines Doppelpunkts (:) in der URL ist die Art und Weise, wie wir ein dynamisches Segment in unserer URL definieren. Nach dem Doppelpunkt geben wir den Namen des Segments an, das in unserem Fall id heißt und der ID eines bestimmten To-Do-Elements zugeordnet ist. Wir können auf den Wert des Segments über das request.params-Objekt zugreifen, wobei der Eigenschaftsname dem Segmentnamen entspricht – request.params.id. Dann verwenden wir das Schema, um ein Element mit derselben ID aus der Mirage-Datenbank zu entfernen.
Wenn Sie bemerkt haben, sind alle meine bisherigen Routen mit api/ präfixiert. Das wiederholte Schreiben kann umständlich sein und Sie möchten es vielleicht einfacher machen. Mirage bietet die Eigenschaft namespace, die helfen kann. Innerhalb des Routen-Hooks können wir die Eigenschaft namespace definieren, damit wir sie nicht jedes Mal schreiben müssen.
routes() {
// Prefix for all routes.
this.namespace = '/api';
this.get('/tasks', () => { ... })
this.delete('/tasks/:id', () => { ... })
this.post('/tasks', () => { ... })
}
OK, integrieren wir das in eine bestehende App
Bisher haben wir Mirage in eine neue App integriert. Aber was ist mit dem Hinzufügen von Mirage zu einer bestehenden Anwendung? Mirage kümmert sich darum, damit Sie nicht Ihre gesamte API mocken müssen.
Das erste, was zu beachten ist, ist, dass das Hinzufügen von Mirage zu einer bestehenden Anwendung einen Fehler auslöst, wenn die Seite eine Anfrage stellt, die nicht von Mirage behandelt wird. Um dies zu vermeiden, können wir Mirage anweisen, alle unbehandelten Anfragen weiterzuleiten.
routes() {
this.get('/tasks', () => { ... })
// Pass through all unhandled requests.
this.passthrough()
}
Jetzt können wir auf einer bestehenden API entwickeln, wobei Mirage nur die fehlenden Teile unserer API behandelt.
Mirage kann sogar die Basis-URL ändern, von der aus es Anfragen abfängt. Das ist nützlich, da ein Server normalerweise nicht unter localhost:3000 läuft, sondern unter einer benutzerdefinierten Domain.
routes() {
// Set the base route.
this.urlPrefix = 'https://devenv.ourapp.example';
this.get('/tasks', () => { ... })
}
Jetzt werden alle unsere Anfragen auf den echten API-Server zeigen, aber Mirage wird sie abfangen, wie es das getan hat, als wir es mit einer neuen App eingerichtet haben. Das bedeutet, dass der Übergang von Mirage zur echten API ziemlich nahtlos ist – löschen Sie die Route vom Mock-Server und die Dinge sind einsatzbereit.
Zusammenfassung
Im Laufe von fünf Jahren habe ich viele Mocking-Frameworks verwendet, aber ich mochte nie wirklich eine der vorhandenen Lösungen. Bis vor kurzem, als mein Team eine Mocking-Lösung benötigte und ich Mirage entdeckte.
Andere Lösungen, wie der weit verbreitete JSON-Server, sind externe Prozesse, die neben dem Frontend laufen müssen. Darüber hinaus sind sie oft nichts weiter als ein Express-Server mit Utility-Funktionen obendrauf. Das Ergebnis ist, dass Frontend-Entwickler wie wir etwas über Middleware, NodeJS und wie Server funktionieren, wissen müssen… Dinge, mit denen viele von uns wahrscheinlich nichts zu tun haben wollen. Andere Versuche, wie Mockoon, haben eine komplexe Benutzeroberfläche und fehlen dringend benötigte Funktionen. Es gibt eine weitere Gruppe von Frameworks, die nur zum Testen verwendet werden, wie das beliebte SinonJS. Leider können diese Frameworks nicht verwendet werden, um das normale Verhalten zu mocken.
Mein Team konnte einen funktionierenden Server erstellen, der es uns ermöglicht, Frontend-Code zu schreiben, als ob wir mit einem echten Backend arbeiten würden. Wir haben dies erreicht, indem wir die Frontend-Codebasis ohne externe Prozesse oder Server geschrieben haben, die zum Ausführen benötigt werden. Deshalb liebe ich Mirage. Es ist sehr einfach einzurichten und dennoch leistungsfähig genug, um alles zu bewältigen, was ihm begegnet. Sie können es für einfache Anwendungen, die ein statisches Array zurückgeben, bis hin zu vollwertigen Backend-Apps verwenden – unabhängig davon, ob es sich um eine neue oder eine bestehende App handelt.
Es gibt noch viel mehr zu Mirage jenseits der hier behandelten Implementierungen. Ein funktionierendes Beispiel dessen, was wir behandelt haben, finden Sie auf GitHub. (Fun Fact: Mirage funktioniert auch mit GraphQL!) Mirage hat gut geschriebene Dokumentation, die viele Schritt-für-Schritt-Tutorials enthält, also schauen Sie sich diese unbedingt an.
Großartig, danke!!
Mock Service Worker ist eine weitere API-Mocking-Bibliothek. Ich hatte vorher nichts von Mirage gehört, aber es sieht MSW sehr ähnlich.
https://mswjs.io/
Das ist auch ein toller Vorschlag. Ich habe es noch nicht ausprobiert, denn als ich nach einer Lösung suchte, tauchte zuerst Mirage auf. Eine Sache, die ich bei MSW vermisse, ist die ORM, korrigieren Sie mich, wenn ich falsch liege. Eine einfache Funktion, die Sie mit
Map()implementieren können, aber dennoch eine wirklich nützliche, wenn Sie den Zustand aktualisiert halten müssen.Vielen Dank für die Antwort, ich werde MSW auf jeden Fall ausprobieren.
Ich hatte gehofft, dass „frontend-freundlich“ bedeuten würde, dass es eine GUI hat, mit der man schnell APIs prototypisieren kann, ohne Express oder ein anderes Backend-Framework lernen oder verwenden zu müssen.
Es stellt sich heraus, dass es genau der gleiche Workflow ist wie beim Einrichten einer Express-Mock-API, nur ohne deutliche Vorteile.
Danke für den Hinweis auf MSW! Wenn man Cypress zum Testen verwendet, bietet eine API-Mocking-Bibliothek wie Mirage oder MSW dann immer noch so viel Wert?
@Mike
Sie können es mit Cypress verwenden, ich mache das regelmäßig. Grundsätzlich ist das Setup das gleiche. Sie erstellen einen Server mit der
environmentauf ‚test‘ gesetzt und los geht’s.Jetzt können Sie in Ihren Tests
server.create('user', {something})verwenden und beim Besuch der Seite prüfen, ob diese Daten vorhanden sind.@Marko: Danke für Ihre Antwort. Mir ist nicht klar, warum Sie Mirage (oder MSW) mit Cypress anstelle von nur Fixtures verwenden würden…
@Mike Nun, es gibt einen Unterschied. Die Verwendung von Fixtures in Cypress (oder einem anderen Testframework) bedeutet, dass Sie Ihre Anfrage stubben müssen. Das bedeutet, die Antwort
cy.request()abzufangen und sie durch das Fixture zu ersetzen. Durch die Verwendung und das Mocken des Servers (egal welcher Art) testen Sie Ihre App normal, ohne Änderungen, und halten Ihre Tests sauber.Darüber hinaus versuche ich, die DRY- und KISS-Prinzipien zu befolgen. Ich richte den Mock-Server nur einmal ein und verwende ihn an beiden Stellen, um eine „Coding“-Umgebung zu testen. Die Art und Weise, wie ich über einen Mock-Server denke, ist wie über meinen privat kontrollierten Dev-Server, wo ich die Daten kontrollieren kann und keine Benutzer beeinträchtige.
Im Grunde genommen kommt es, wie bei allem im Leben, auf die persönliche Vorliebe an. Sie können Ihre Anfrage stubben, Sie können eine gemockte API erstellen oder Sie testen gar nicht.
Msw sieht vielversprechender aus als Mirage, da es Ihnen hilft, die Anfragen über den Netzwerk-Tab zu sehen. Das erleichtert das Debugging erheblich.
Ehrlich gesagt, es stört mich nicht, ich mag es irgendwie. So wie ich es sehe, behalte ich die echten Anfragen im
Network-Tab, ich kann sie schnell überfliegen. Alle gemockten, Dummy-Anfragen werden schön in der Konsole protokolliert, wie Sie es normalerweise für Entwicklungszwecke tun.Bezüglich MSW, wie ich bereits im vorherigen Kommentar sagte, sieht es wirklich gut aus, aber auf den ersten Blick fehlt die ORM. Ich werde es für ein kleineres Projekt ausprobieren.
Ich habe https://mockaroo.com letztes Jahr gefunden und seitdem intensiv genutzt. Es ist super einfach zu bedienen und ich genieße es wirklich, wenn ich einen neuen API-Endpunkt erstellen kann und mit all den Optionen spielen kann, die es für die Generierung von Dummy- oder benutzerdefinierten Daten bietet.
Mockaroo ist großartig, ich benutze es schon seit Ewigkeiten, um alle Arten von Zufallsdaten zu generieren. Aber ich habe es nie für API-Mocking verwendet und es gibt ein paar Gründe, warum ich mich von solchen Lösungen fernhalte.
Dienste wie Mockaroo oder sagen wir Mocky sind externe Dienste, die außerhalb Ihrer Anwendung leben. Das kann ein Problem sein, wenn Sie ein Team von Entwicklern haben, das ein privates Konto erstellen müsste, um Endpunkte zu bearbeiten, zu aktualisieren oder zu erstellen. Darüber hinaus ist dies eine weitere Komplexitätsebene für das Unternehmen, die verwaltet werden muss. Benötigen beispielsweise alle Entwickler ein kostenpflichtiges Konto? Was passiert, wenn ein Entwickler das Unternehmen verlässt, wie entzieht man den Zugriff?
Aus genau diesen Gründen mag ich Tools wie Mirage und das oben erwähnte MSW. Sie bringen die volle API-Mocking-Funktionalität direkt in Ihren Code, Sie müssen Ihren Editor nicht verlassen, um etwas zu aktualisieren. Ein Bonus ist die Versionskontrolle und die PR-Prüfung.
Für Entwickler, die Mocking-Funktionen suchen, ohne Code schreiben zu müssen, kann ich die Browser-Erweiterung Tweak sehr empfehlen: https://tweak-extension.com/
Dies ist ein großartiger Überblick. Als Ingenieur ergibt Mocking in Laravel manchmal einfach keinen Sinn für mich. Ihr Tutorial ist ein großartiger Überblick. Ich hoffe, ich kann dies wieder aufgreifen, wenn ich mich in Zukunft mit Frontend-Tests wie diesem beschäftige. Danke!
Vielen Dank für den netten Kommentar. Mirage zu finden war ein Durchbruch für mich, es hat meine Art zu entwickeln verändert. Es war wirklich einfach, das gesamte Team onboarden.
Ich habe das Gefühl, wenn man das Mock-Framework eingerichtet hat, ist das Backend schon fertig lol