Controller zu testen ist nicht die einfachste Sache der Welt. Nun, lassen Sie mich das anders formulieren: Das Testen ist ein Kinderspiel; was zuerst schwierig ist, ist zu bestimmen Was zu testen.
Soll ein Controller-Test den Text auf der Seite überprüfen? Soll es die Datenbank berühren? Soll sichergestellt werden, dass Variablen in der Ansicht vorhanden sind? Wenn dies Ihre erste Heu-Fahrt ist, können diese Dinge verwirrend sein! Lass mich helfen.
Controller-Tests sollten die Antworten überprüfen, sicherstellen, dass die richtigen Datenbankzugriffsmethoden ausgelöst werden, und sicherstellen, dass die entsprechenden Instanzvariablen an die Ansicht gesendet werden.
Das Testen eines Controllers kann in drei Teile unterteilt werden.
Aussicht
).Der beste Weg, um diese Dinge zu lernen, ist durch Beispiele. Hier ist die "Hallo Welt"von Controller-Tests in Laravel.
client-> request ('GET', 'posts');
Laravel nutzt eine Handvoll Symfony-Komponenten, um das Testen von Routen und Ansichten zu vereinfachen, einschließlich HttpKernel, DomCrawler und BrowserKit. Aus diesem Grund ist es wichtig, dass Ihre PHPUnit-Tests erben und nicht PHPUnit \ _Framework \ _TestCase
, aber Testfall
. Keine Sorge, Laravel erweitert die Vorgängerversion noch, aber es hilft beim Einrichten der Laravel-App zum Testen und bietet eine Vielzahl von Hilfsassertionsmethoden, die Sie verwenden sollten. Mehr dazu in Kürze.
Im obigen Code-Snippet machen wir eine ERHALTEN
Anfrage zu / Beiträge
, oder localhost: 8000 / Beiträge
. Unter der Annahme, dass diese Zeile zu einer neuen Installation von Laravel hinzugefügt wird, wirft Symfony ein NotFoundHttpException
. Wenn Sie mitarbeiten, probieren Sie es aus, indem Sie ausführen phpunit
von der Kommandozeile aus.
$ phpunit 1) PostsControllerTest :: testIndex Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException:
Im menschliches sprechen, dies bedeutet im Wesentlichen "Hey, ich habe versucht, diese Route anzurufen, aber Sie haben nichts registriert, Dummkopf!"
Wie Sie sich vorstellen können, ist diese Art von Anforderung so weit verbreitet, dass es sinnvoll ist, eine Hilfsmethode anzugeben, wie z $ this-> call ()
. In der Tat macht Laravel genau das! Das bedeutet, dass das vorherige Beispiel wie folgt umgestaltet werden kann:
# app / tests / controller / postsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts');
Obwohl wir in diesem Kapitel bei der Basisfunktionalität bleiben, gehe ich in meinen persönlichen Projekten einen Schritt weiter, indem ich solche Methoden wie zulasse $ this-> get ()
, $ this-> post ()
, usw. Dank PHP-Überladung muss dazu lediglich eine einzige Methode hinzugefügt werden, die Sie hinzufügen können app / tests / TestCase.php
.
# app / tests / TestCase.php öffentliche Funktion __call ($ method, $ args) if (in_array ($ method, ['get', 'post', 'put', 'patch', 'delete']))) $ this-> call zurückgeben ($ method, $ args [0]); werfen Sie neue BadMethodCallException;
Nun können Sie schreiben $ this-> get ('posts')
und erzielen Sie genau das gleiche Ergebnis wie in den beiden vorangegangenen Beispielen. Wie bereits erwähnt, bleiben wir der Einfachheit halber jedoch bei der Basisfunktionalität des Frameworks.
Um den Test zu bestehen, müssen wir nur die richtige Route vorbereiten.
Laufen
phpunit
wird uns wieder zum Grün bringen.
Helfer-Behauptungen von Laravel
Ein Test, bei dem Sie wiederholt schreiben, stellt sicher, dass ein Controller eine bestimmte Variable an eine Ansicht übergibt. Zum Beispiel die
Index
Methode vonPostsController
sollte a passieren$ Beiträge
Variable in der zugehörigen Ansicht, oder? Auf diese Weise kann die Ansicht alle Beiträge filtern und auf der Seite anzeigen. Dies ist ein wichtiger Test zum Schreiben!Wenn es eine so gewöhnliche Aufgabe ist, wäre es nicht noch einmal sinnvoll, dass Laravel eine helfende Behauptung vorbringt, um genau diese Sache zu vollbringen? Natürlich würde es. Und natürlich auch Laravel!
Illuminate \ Foundation \ Testing \ TestCase
Enthält eine Reihe von Methoden, mit denen die für die Durchführung grundlegender Zusicherungen erforderliche Code-Menge drastisch reduziert wird. Diese Liste enthält:
assertViewHas
assertResponseOk
assertRedirectedTo
assertRedirectedToRoute
assertRedirectedToAction
assertSessionHas
assertSessionHasErrors
Die folgenden Beispiele rufen auf GET / Beiträge
und prüft, ob seine Ansichten die Variable erhalten, $ Beiträge
.
# app / tests / controller / postsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts');
Spitze: Bei der Formatierung ziehe ich es vor, einen Zeilenumbruch zwischen der Assertion eines Tests und dem Code vorzubereiten, der die Phase vorbereitet.
assertViewHas
ist einfach ein bisschen Zucker, der das Antwortobjekt inspiziert - von dem es zurückgegeben wird $ this-> call ()
- und prüft, ob die mit der Ansicht verknüpften Daten a enthalten Beiträge
Variable.
Bei der Untersuchung des Antwortobjekts haben Sie zwei Hauptoptionen.
$ response-> getOriginalContent ()
: Holen Sie den ursprünglichen Inhalt oder den zurückgegebenen Inhalt Aussicht
. Optional können Sie auf die Original
Eigenschaft direkt, anstatt die getOriginalContent
Methode.$ response-> getContent ()
: Holen Sie die gerenderte Ausgabe. Wenn eine Aussicht
Die Instanz wird dann von der Route zurückgegeben getContent ()
ist gleich der HTML-Ausgabe. Dies kann für DOM-Überprüfungen hilfreich sein, z. B. "Die Ansicht muss diese Zeichenfolge enthalten."Nehmen wir an, dass die Beiträge
Route besteht aus:
Sollten wir rennen?
phpunit
, es wird mit einem hilfreichen kreischen nächster Schritt Botschaft:1) postsControllerTest :: testIndex Fehlgeschlagenes Bestätigen, dass ein Array den Schlüssel "posts" hat.Um es grün zu machen, holen wir einfach die Beiträge ab und übergeben sie an die Ansicht.
# app / routes.php Route :: get ('posts', function () $ posts = Post :: all (); return Ansicht :: make ('posts.index', ['posts', $ posts]) ;);Dabei ist zu beachten, dass der Code nach dem derzeitigen Stand nur für die Variable sorgt,
$ Beiträge
, wird an die Ansicht übergeben. Es prüft nicht seinen Wert. DasassertViewHas
Akzeptiert optional ein zweites Argument, um den Wert der Variablen sowie deren Vorhandensein zu überprüfen.# app / tests / controller / postsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts', 'foo');Mit diesem geänderten Code verfügt die Ansicht über eine Variable,
$ Beiträge
, das ist gleichfoo
, Der Test wird fehlschlagen. In dieser Situation ist es jedoch wahrscheinlich, dass wir lieber keinen Wert angeben, sondern erklären, dass der Wert eine Instanz von Laravel istIlluminate \ Database \ Eloquent \ Collection
Klasse. Wie können wir das schaffen? PHPUnit bietet eine hilfreiche HilfeassertInstanceOf
Behauptung, diese Notwendigkeit zu erfüllen!# app / tests / controller / postsControllerTest.php öffentliche Funktion testIndex () $ response = $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts'); // getData () gibt alle an die Antwort angefügten Variablen zurück. $ posts = $ response-> original-> getData () ['posts']; $ this-> assertInstanceOf ('Illuminate \ Database \ Eloquent \ Collection', $ posts);Mit dieser Modifikation haben wir das zum Controller erklärt Muss bestehen
$ Beiträge
- eine Instanz vonIlluminate \ Database \ Eloquent \ Collection
- zur Ansicht. Ausgezeichnet.
Verspottung der Datenbank
Bei unseren Tests gibt es ein krasses Problem. Hast du es gefangen?
Für jeden Test wird eine SQL-Abfrage in der Datenbank ausgeführt. Obwohl dies für bestimmte Arten von Tests (Akzeptanz, Integration) nützlich ist, dient dies für grundlegende Controller-Tests nur zur Leistungsminderung.
Ich habe dies an diesem Punkt mehrmals in deinen Schädel gebohrt. Wir sind nicht daran interessiert, Eloquents Fähigkeit zu testen, Datensätze aus einer Datenbank abzurufen. Es hat eigene Tests. Taylor weiß, dass es funktioniert! Verschwenden Sie keine Zeit und Rechenleistung, wenn Sie dieselben Tests wiederholen.
Stattdessen ist es am besten, die Datenbank zu verspotten und lediglich zu überprüfen, ob die entsprechenden Methoden mit den richtigen Argumenten aufgerufen werden. Oder mit anderen Worten, wir wollen das sicherstellen
Post :: all ()
feuert niemals und schlägt auf die Datenbank. Wir wissen, dass dies funktioniert, daher sind keine Tests erforderlich.Dieser Abschnitt hängt stark von der Mockery-Bibliothek ab. Bitte lesen Sie dieses Kapitel aus meinem Buch durch, falls Sie noch nicht damit vertraut sind.
Erforderliches Refactoring
Leider haben wir den Code bisher so strukturiert, dass ein Test praktisch unmöglich ist.
# app / routes.php Route :: get ('posts', function () // Autsch. Das können wir nicht testen !! $ posts = Post :: all (); return View :: make ('posts.) index ') -> with (' posts ', $ posts););Genau aus diesem Grund gilt es als schlechte Praxis, Eloquent-Anrufe in Ihre Controller einzuschachteln. Verwechseln Sie nicht die Fassaden von Laravel, die überprüfbar sind und mit Spottchen ausgetauscht werden können (
Queue :: shouldReceive ()
), mit Ihren Eloquent-Modellen. Die Lösung besteht darin, die Datenbankschicht über den Konstruktor in den Controller zu injizieren. Dies erfordert etwas Refactoring.Warnung: Das Speichern von Logik in Route Callbacks ist für kleine Projekte und APIs hilfreich, macht das Testen jedoch unglaublich schwierig. Verwenden Sie für Anwendungen von beträchtlicher Größe Controller.
Lassen Sie uns eine neue Ressource registrieren, indem Sie die
Beiträge
Route mit:# app / routes.php Route :: resource ('posts', 'PostsController');… Und erzeuge mit Artisan den notwendigen einfallsreichen Controller.
$ php Handwerker-Controller: Erstellen Sie den PostsController-Controller erfolgreich!Nun, anstatt auf das zu verweisen
Post
direkt modellieren, injizieren wir es in den Konstruktor der Steuerung. Hier ist ein komprimiertes Beispiel, in dem alle restful-Methoden außer derjenigen, die wir derzeit testen möchten, weggelassen werden.post = $ post; public function index () $ posts = $ this-> post-> all (); return View :: make ('posts.index') -> with ('posts', $ posts);Bitte beachten Sie, dass es besser ist, ein Interface einzugeben, anstatt auf das Eloquent-Modell selbst zu verweisen. Aber eine Sache zur Zeit! Lasst uns darauf hinarbeiten.
Dies ist eine wesentlich bessere Möglichkeit, den Code zu strukturieren. Da das Modell jetzt injiziert ist, können wir es mit einer verspotteten Version zum Testen austauschen. Hier ist ein Beispiel dafür:
Schein = Spott :: Schein ('Eloquent', 'Post'); public function tearDown () Mockery :: close (); public function testIndex () $ this-> mock -> shouldReceive ('all') -> once () -> andReturn ('foo'); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts');Der Hauptvorteil dieser Umstrukturierung ist, dass die Datenbank jetzt niemals unnötig angegriffen wird. Stattdessen verifizieren wir mit Mockery lediglich, dass
alles
Methode wird am Modell ausgelöst.$ this-> mock -> shouldReceive ('all') -> once ();Leider, wenn Sie auf die Kodierung einer Schnittstelle verzichten und stattdessen die
Post
Wenn Sie Modell in den Controller einbauen, müssen Sie ein wenig Trick spielen, um Eloquents Statik zu umgehen, die mit Mockery kollidieren kann. Deshalb entführen wir beidePost
undBeredt
Klassen im Konstruktor des Tests, bevor die offiziellen Versionen geladen wurden. Auf diese Weise haben wir einen klaren Plan, um alle Erwartungen zu erklären. Der Nachteil ist natürlich, dass wir mit den Mockery-Methoden, wie z. B., keine vorhandenen Methoden verwenden könnenmakePartial ()
.Der IoC-Container
Der IoC-Container von Laravel vereinfacht das Einfügen von Abhängigkeiten in Ihre Klassen erheblich. Jedes Mal, wenn ein Controller angefordert wird, wird er aus dem IoC-Container heraus aufgelöst. Als solche, wenn wir erklären müssen, dass eine Scheinversion von
Post
sollte zum Testen verwendet werden, müssen wir Laravel nur die Instanz von angebenPost
das sollte verwendet werden.$ this-> app-> instance ('Post', $ this-> mock);Denken Sie an diesen Code als "Hey Laravel, wenn Sie eine Instanz von brauchen
Post
, Ich möchte, dass Sie meine verspottete Version verwenden."Weil die App das erweitertContainer
, Wir haben Zugriff auf alle IoC-Methoden direkt von dort.Bei der Instantiierung des Controllers nutzt Laravel die Möglichkeiten der PHP-Reflektion, um den Typus zu lesen und die Abhängigkeit für Sie einzufügen. Stimmt; Sie müssen keine einzige Bindung schreiben, um dies zuzulassen. es ist automatisiert!
Weiterleitungen
Eine andere häufige Erwartung, dass Sie schreiben werden, ist eine, die sicherstellt, dass der Benutzer an den richtigen Speicherort weitergeleitet wird, möglicherweise wenn Sie einen neuen Beitrag zur Datenbank hinzufügen. Wie können wir das erreichen??
# app / tests / controller / PostsControllerTest.php öffentliche Funktion testStore () $ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.index');Wenn wir davon ausgehen, dass wir einem ruhigen Charakter folgen, würden wir einen neuen Beitrag hinzufügen
POST
zur Sammlung oderBeiträge
(verwechsle nicht diePOST
Anforderungsmethode mit dem Ressourcennamen, der zufällig denselben Namen hat).$ this-> call ('POST', 'posts');Dann müssen wir nur noch eine von Laravels Helferaussagen einsetzen,
assertRedirectedToRoute
.Spitze: Wenn eine Ressource bei Laravel registriert ist (
Route :: Ressource ()
), registriert das Framework automatisch die benannten Routen. LaufPHP Handwerker Routen
wenn Sie jemals vergessen, was diese Namen sind.Vielleicht möchten Sie auch sicherstellen, dass die
$ _POST
Superglobal wird an die übergebenerstellen
Methode. Auch wenn wir ein Formular nicht physisch übermitteln, können wir dies über dieEingabe :: replace ()
Methode, die uns erlaubt, dieses Array zu "stubben". Hier ist der modifizierte Test, der Mockery's verwendetmit()
Methode, um die Argumente zu überprüfen, die an die Methode übergeben wurden, auf die von verwiesen wirdsollte erhalten
.# app / tests / controller / PostsControllerTest.php public function testStore () Eingabe :: replace ($ input = ['title' => 'My Title']); $ this-> mock -> shouldReceive ('create') -> once () -> with ($ input); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.index');
Pfade
Eine Sache, die wir in diesem Test nicht berücksichtigt haben, ist die Validierung. Es sollte zwei getrennte Pfade durch die geben
Geschäft
Methode, abhängig davon, ob die Validierung erfolgreich ist:
- Leiten Sie zurück zum Formular "Post erstellen" und zeigen Sie die Fehler bei der Formularprüfung an.
- Umleitung zur Sammlung oder zur benannten Route,
posts.index
.Als bewährte Methode sollte jeder Test nur einen Pfad durch Ihren Code darstellen.
Dieser erste Pfad wird für eine nicht erfolgreiche Überprüfung verwendet.
# app / tests / controller / PostsControllerTest.php public function testStoreFails () // Stufe für fehlgeschlagene Überprüfung setzen Eingabe :: replace (['title' => "]); $ this-> app-> instance ('Post ', $ this-> mock); $ this-> call (' POST ',' posts '); // Die Validierung ist fehlgeschlagen und das Erstellungsformular $ this-> assertRedirectedToRoute (' posts.create ') muss neu geladen werden. // Die Fehler sollte an die Ansicht gesendet werden $ this-> assertSessionHasErrors (['title']);Das obige Code-Snippet legt explizit fest, welche Fehler vorhanden sein sollen. Alternativ können Sie das Argument unter weglassen
assertSessionHasErrors
, In diesem Fall wird lediglich überprüft, ob ein Nachrichtenbestand geflasht wurde (in der Übersetzung ist die Umleitung enthalten)withErrors ($ Fehler)
).Nun zum Test, der die erfolgreiche Validierung abdeckt.
# app / tests / controller / PostsControllerTest.php public function testStoreSuccess () // Phase für erfolgreiche Validierung einstellen Eingabe :: replace (['title' => 'Foo Title']); $ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); // Sollte zur Sammlung weiterleiten, mit einer erfolgreichen Flash-Nachricht $ this-> assertRedirectedToRoute ('posts.index', ['flash']);Der Produktionscode für diese beiden Tests könnte folgendermaßen aussehen:
# app / controller / PostsController.php public function store () $ input = Eingabe :: all (); // Zur Vereinfachung führen wir die Validierung im Controller aus. // Sie sollten dies in das Modell oder einen Dienst exportieren. if ($ v-> failed ()) return Umleitung :: route ('posts.create') -> withInput () -> withErrors ($ v-> messages ()); $ this-> post-> create ($ input); return Redirect :: route ('posts.index') -> with ('flash', 'Ihr Beitrag wurde erstellt!');Beachten Sie, wie die
Validator
ist direkt im Controller verschachtelt? Im Allgemeinen würde ich empfehlen, dass Sie dies zu einem Dienst zusammenfassen. Auf diese Weise können Sie Ihre Validierung isoliert von beliebigen Controllern oder Routen testen. Trotzdem lassen wir die Dinge der Einfachheit halber so, wie sie sind. Eine Sache, die wir im Hinterkopf behalten sollten, ist, dass wir uns nicht darüber lustig machenValidator
, obwohl Sie es sicherlich tun könnten. Da es sich bei dieser Klasse um eine Fassade handelt, kann sie leicht mit einer verspotteten Version über die Facade ausgetauscht werdensollte erhalten
Methode, ohne dass wir uns Gedanken machen müssen, eine Instanz durch den Konstruktor einzufügen. Sieg!# app / controller / PostsController.php Validator :: shouldReceive ('make') -> once () -> andReturn (Mockery :: mock (['failed' => 'true']));Von Zeit zu Zeit werden Sie feststellen, dass eine Methode, die verspottet werden muss, ein Objekt selbst zurückgeben soll. Glücklicherweise ist dies mit Mockery ein Kinderspiel: Wir müssen nur einen anonymen Mock erstellen und ein Array übergeben, das den Methodennamen bzw. den Antwortwert signalisiert. So wie:
Spott :: Schein (['fail' => 'true'])bereitet ein Objekt vor, das ein enthält
schlägt fehl ()
Methode, die zurückgibtwahr
.
Repositories
Um eine optimale Flexibilität zu ermöglichen, anstatt eine direkte Verbindung zwischen Ihrem Controller und einem ORM herzustellen, wie beispielsweise Eloquent, ist es besser, in eine Schnittstelle zu codieren. Der erhebliche Vorteil dieses Ansatzes besteht darin, dass Sie Eloquent beispielsweise für Mongo oder Redis austauschen müssen, wenn dies buchstäblich die Änderung einer einzelnen Zeile erfordert. Noch besser, der Controller muss niemals berührt werden.
Repositorys repräsentieren die Datenzugriffsebene Ihrer Anwendung.
Was könnte eine Schnittstelle zum Verwalten der Datenbankschicht von a
Post
aussehen wie? Damit sollten Sie anfangen.Dies kann sicherlich erweitert werden, aber wir haben die minimalen Methoden für die Demo hinzugefügt:
alles
,finden
, understellen
. Beachten Sie, dass die Repository-Schnittstellen in gespeichert werdenApp / Repositories
. Da dieser Ordner standardmäßig nicht automatisch geladen wird, muss der Ordner aktualisiert werdencomposer.json
Datei, auf die die Anwendung verweist.// composer.json "autoload": "classmap": [//… "app / repositories"]Vergessen Sie nicht, wenn eine neue Klasse zu diesem Verzeichnis hinzugefügt wird
composer dump-autoload -o
. Das-O
, (optimieren) flag ist optional, sollte jedoch immer als bewährte Methode verwendet werden.Wenn Sie versuchen, diese Schnittstelle in Ihren Controller einzuspeisen, schnappt Laravel Sie an. Gehen Sie geradeaus; Probieren Sie es aus und sehen Sie. Hier ist das modifiziert
PostController
, die aktualisiert wurde, um eine Schnittstelle zu injizieren, anstatt diePost
Eloquentes Modell.post = $ post; public function index () $ posts = $ this-> post-> all (); return View :: make ('posts.index', ['posts' => $ posts]);Wenn Sie den Server ausführen und die Ausgabe anzeigen, werden Sie mit der gefürchteten (aber schönen) Whoops-Fehlerseite empfangen, die "PostRepositoryInterface ist nicht instanziierbar."
Wenn Sie darüber nachdenken, quietscht der Rahmen natürlich! Laravel ist intelligent, aber kein Gedankenleser. Es muss mitgeteilt werden, welche Implementierung der Schnittstelle in der Steuerung verwendet werden soll.
Im Moment fügen wir diese Bindung zu hinzu
app / routes.php
. Später verwenden wir stattdessen Dienstanbieter, um diese Art von Logik zu speichern.# app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ EloquentPostRepository');Verbalisieren Sie diesen Funktionsaufruf als "Laravel, Baby, wenn du einen Fall brauchst
PostRepositoryInterface
, Ich möchte, dass du es benutztEloquentPostRepository
."
App / Repositorys / EloquentPostRepository
wird einfach eine Hülle um Eloquent sein, die implementiertPostRepositoryInterface
. Auf diese Weise beschränken wir die API (und jede andere Implementierung) nicht auf Eloquents Interpretation. Wir können die Methoden benennen, wie wir es wünschen.Einige mögen argumentieren, dass die
Post
Das Modell sollte zu Testzwecken in diese Implementierung eingefügt werden. Wenn Sie damit einverstanden sind, fügen Sie es wie üblich einfach durch den Konstruktor ein.Das ist alles was es braucht! Aktualisieren Sie den Browser, und die Dinge sollten wieder normal sein. Erst jetzt ist Ihre Anwendung viel besser strukturiert und der Controller ist nicht mehr mit Eloquent verbunden.
Stellen wir uns vor, dass Ihr Chef Ihnen in einigen Monaten mitteilt, dass Sie Eloquent mit Redis austauschen müssen. Nun, da Sie Ihre Anwendung so zukunftssicher strukturiert haben, müssen Sie nur die neue erstellen
App / Repositorys / RedisPostRepository
Implementierung:Und aktualisieren Sie die Bindung:
# app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ RedisPostRepository');Sofort nutzen Sie Redis in Ihrem Controller. Beachte wie
app / controller / PostsController.php
wurde nie berührt Das ist das Schöne daran!
Struktur
In dieser Lektion hat unsere Organisation bisher etwas gefehlt. IoC-Bindungen im
routen.php
Datei? Alle Repositories in einem Verzeichnis zusammengefasst? Sicher, das mag am Anfang funktionieren, aber es wird schnell klar, dass das nicht skaliert.Im letzten Abschnitt dieses Artikels werden wir unseren Code für PSR festlegen und Dienstanbieter dazu nutzen, alle anwendbaren Bindungen zu registrieren.
PSR-0 definiert die zwingenden Anforderungen, die für die Interoperabilität des Autoloaders einzuhalten sind.
Ein PSR-0-Lader kann über den PC mit dem Composer registriert werden
psr-0
Objekt.// composer.json "autoload": "psr-0": "Way": "app / lib /"Die Syntax kann zunächst verwirrend sein. Es war auf jeden Fall für mich. Ein einfacher Weg zu entschlüsseln
"Weg": "app / lib /"
ist zu dir selbst zu denken "Der Basisordner für dieWeg
Namespace befindet sich inapp / lib
."Ersetzen Sie natürlich meinen Nachnamen durch den Namen Ihres Projekts. Die dazu passende Verzeichnisstruktur wäre:
Als nächstes, anstatt alle Repositories in einem Repositories
Ein eleganterer Ansatz könnte sein, sie in mehrere Verzeichnisse zu kategorisieren, wie zB:
Es ist wichtig, dass wir uns an diese Namens- und Ordnerkonvention halten, wenn das automatische Laden wie erwartet funktionieren soll. Sie müssen nur noch die Namespaces aktualisieren PostRepositoryInterface
und EloquentPostRepository
.
Und für die Umsetzung:
Da gehen wir; das ist viel sauberer. Aber was ist mit diesen lästigen Bindungen? Die Routendatei ist zwar ein praktischer Ort zum Experimentieren, aber es macht wenig Sinn, sie dauerhaft dort zu speichern. Stattdessen verwenden wir Dienstanbieter.
Dienstanbieter sind nichts anderes als Bootstrap - Klassen, mit denen Sie beliebige Aktionen ausführen können: Registrieren Sie eine Bindung, haken Sie sich in ein Ereignis ein, importieren Sie eine Routendatei usw.
Ein Dienstanbieter
registrieren()
wird automatisch von Laravel ausgelöst.app-> bind ('Way \ Storage \ Post \ PostRepositoryInterface', 'Way \ Storage \ Post \ EloquentPostRepository');Um diese Datei Laravel bekannt zu machen, müssen Sie sie nur einbinden
app / config / app.php
, innerhalb desAnbieter
Array.# app / config / app.php 'provider' => array ('Illuminate \ Foundation \ Providers \ ArtisanServiceProvider', 'Illuminate \ Auth \ AuthServiceProvider', //… 'Weg \ Storage \ StorageServiceProvider')Gut; Jetzt haben wir eine spezielle Datei zum Registrieren neuer Bindungen.
Aktualisieren der Tests
Mit unserer neuen Struktur, anstatt das eloquente Modell selbst zu verspotten, können wir stattdessen verspotten
PostRepositoryInterface
. Hier ist ein Beispiel eines solchen Tests:# app / tests / controller / PostsControllerTest.php öffentliche Funktion testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ mock-> shouldReceive ('all') -> einmal (); $ this-> app-> instance ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts');Das können wir jedoch verbessern. Es liegt nahe, dass jede Methode innerhalb
PostsControllerTest
wird eine gespielte Version des Repositorys erfordern. Daher ist es besser, einige dieser Vorbereitungsarbeiten in eine eigene Methode zu extrahieren:# app / tests / controller / PostsControllerTest.php public function setUp () parent :: setUp (); $ this-> mock ('Way \ Storage \ Post \ PostRepositoryInterface'); public function mock ($ class) $ mock = Mockery :: mock ($ class); $ this-> app-> instance ($ class, $ mock); return $ mock; public function testIndex () $ this-> mock-> shouldReceive ('all') -> einmal (); $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts');Nicht schlecht, ay?
Wenn Sie jetzt superfliegen wollen und bereit sind, Ihrem Produktionscode einen Hauch von Testlogik hinzuzufügen, können Sie Ihr Spott auch im Eloquent-Modell durchführen! Dies würde Folgendes ermöglichen:<