Laravel Controllers testen

Het testen van controllers is niet het gemakkelijkste in de wereld. Nou, laat me dat anders formuleren: het testen ervan is een makkie; wat moeilijk is, althans in het begin, is bepalend wat testen.

Moet een controller-test tekst op de pagina verifiëren? Moet het de database raken? Moet het ervoor zorgen dat er variabelen bestaan ​​in de weergave? Als dit je eerste hooi-rit is, kunnen deze dingen verwarrend zijn! Laat me helpen.

Controllertests moeten antwoorden verifiëren, zorgen dat de juiste methoden voor databasetoegang worden geactiveerd en beweren dat de juiste instantievariabelen naar de weergave worden verzonden.

Het testen van een controller kan in drie delen worden verdeeld.

  • Isoleren: Bespotten alle afhankelijkheden (misschien met uitzondering van de Uitzicht).
  • Oproep: Activeer de gewenste controller-methode.
  • Ervoor zorgen: Voer beweringen uit en controleer of de fase correct is ingesteld.

De Hello World van controller testen

De beste manier om deze dingen te leren is door middel van voorbeelden. Hier is de "Hallo Wereld"van controller testen in Laravel.

 client-> request ('GET', 'posts'); 

Laravel maakt gebruik van een handvol Symfony-componenten om het proces van het testen van routes en views, waaronder HttpKernel, DomCrawler en BrowserKit, te vereenvoudigen. Dit is waarom het van het grootste belang is dat je PHPUnit-tests er van erven, niet PHPUnit \ _Framework \ _TestCase, maar testcase. Maak je geen zorgen, Laravel breidt nog steeds het eerste uit, maar het helpt bij het opzetten van de Laravel-app voor het testen, en biedt ook een verscheidenheid aan methoden voor helperassessment die je wordt aangemoedigd om te gebruiken. Daarover binnenkort meer.

In het bovenstaande codefragment maken we een KRIJGEN verzoek om / berichten, of localhost: 8000 / berichten. Ervan uitgaande dat deze lijn wordt toegevoegd aan een nieuwe installatie van Laravel, zal Symfony een a gooien NotFoundHttpException. Als je meewerkt, probeer het dan door te hardlopen PHPUnit vanaf de opdrachtregel.

 $ phpunit 1) PostsControllerTest :: testIndex Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException:

In human-speak, dit vertaalt zich in essentie, "Hé, ik heb geprobeerd die route te bellen, maar je hebt niks geregistreerd, dwaas!"

Zoals je je kunt voorstellen, is dit soort verzoek zo gewoon dat het zinvol is om een ​​helper-methode te bieden, zoals $ This-> call (). Laravel doet precies dat! Dit betekent dat het vorige voorbeeld kan worden aangepast, zoals:

 # app / tests / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); 

Overbelasting is uw vriend

Hoewel we in dit hoofdstuk blijven bij de basisfunctionaliteit, in mijn persoonlijke projecten, ga ik een stap verder door dergelijke methoden toe te staan $ This-> get (), $ This-> post (), enz. Dankzij PHP-overbelasting vereist dit slechts de toevoeging van een enkele methode, waaraan u kunt toevoegen app / testen / TestCase.php.

 # app / tests / TestCase.php openbare functie __call ($ methode, $ args) if (in_array ($ methode, ['get', 'post', 'put', 'patch', 'delete']))  return $ this-> call ($ methode, $ args [0]);  gooi nieuwe BadMethodCallException; 

Nu bent u vrij om te schrijven $ This-> get ( 'posts') en bereik exact hetzelfde resultaat als de vorige twee voorbeelden. Zoals hierboven vermeld, laten we ons echter in eenvoud houden aan de basisfunctionaliteit van het framework.

Om de test te laten slagen, hoeven we alleen de juiste route te maken.

  

hardlopen PHPUnit opnieuw zal ons terug naar groen.


Laravel's Helper Assertions

Een test die u herhaaldelijk zult aantreffen, is een test die ervoor zorgt dat een controller een bepaalde variabele doorgeeft aan een weergave. Bijvoorbeeld de inhoudsopgave methode van PostsController zou moeten gaan a $ posts verander in de bijbehorende weergave, toch? Op die manier kan de weergave alle berichten filteren en deze op de pagina weergeven. Dit is een belangrijke test om te schrijven!

Als het zo vaak een taak is, zou het dan wederom niet logisch zijn als Laravel een helper-bewering zou geven om dit te bereiken? Natuurlijk zou het. En natuurlijk doet Laravel dat!

Verlichten \ Foundation \ Testing \ testcase bevat een aantal methoden die de hoeveelheid code die nodig is om basale beweringen uit te voeren drastisch verminderen. Deze lijst bevat:

  • assertViewHas
  • assertResponseOk
  • assertRedirectedTo
  • assertRedirectedToRoute
  • assertRedirectedToAction
  • assertSessionHas
  • assertSessionHasErrors

De volgende voorbeelden roepen GET / berichten en verifieert dat zijn weergaven de variabele ontvangt, $ posts.

 # app / tests / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posts'); 

Tip: Als het gaat om formatteren geef ik er de voorkeur aan om een ​​lijnbreuk aan te brengen tussen de bewering van een test en de code die de fase voorbereidt.

assertViewHas is gewoon een beetje suiker dat het antwoordobject inspecteert - waarvan teruggestuurd wordt $ This-> call () - en controleert of de gegevens die aan de weergave zijn gekoppeld een bevatten posts veranderlijk.

Bij het inspecteren van het antwoordobject hebt u twee kernkeuzes.

  • $ Response-> getOriginalContent (): Haal de originele inhoud op of retourneer de inhoud Uitzicht. Optioneel kunt u toegang krijgen tot de origineel eigendom rechtstreeks, in plaats van de getOriginalContent methode.
  • $ Response-> getContent (): Haal de gerenderde uitvoer op. Als een Uitzicht instantie wordt geretourneerd van de route en vervolgens Inhoud krijgen() zal gelijk zijn aan de HTML-uitvoer. Dit kan handig zijn voor DOM-verificaties, zoals 'de weergave moet deze string bevatten."

Laten we aannemen dat het posts route bestaat uit:

  

Moeten we vluchten PHPUnit, het zal squawk met een behulpzaam volgende stap bericht:

 1) PostsControllerTest :: testIndex Mislukt beweren dat een array de sleutel 'posts' heeft.

Om het groen te maken, halen we de berichten gewoon op en geven deze door aan de weergave.

 # app / routes.php Route :: get ('posts', function () $ posts = Post :: all (); return View :: make ('posts.index', ['posts', $ posts]) ;);

Een ding om in gedachten te houden is dat, zoals de code momenteel staat, het alleen zorgt voor de variabele, $ posts, wordt doorgegeven aan de weergave. Het inspecteert de waarde ervan niet. De assertViewHas accepteert optioneel een tweede argument om de waarde van de variabele te verifiëren, evenals het bestaan ​​ervan.

 # app / tests / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('posts', 'foo'); 

Met deze gewijzigde code, unles de weergave heeft een variabele, $ posts, dat is gelijk aan foo, de test zal mislukken. In deze situatie is het echter waarschijnlijk dat we liever geen waarde opgeven, maar in plaats daarvan verklaren dat de waarde een exemplaar is van Laravel's Verlichten \ Database \ Welsprekend \ Collection klasse. Hoe kunnen we dat bereiken? PHPUnit biedt een hulp assertInstanceOf bewering om aan deze behoefte te voldoen!

 # app / tests / controllers / PostsControllerTest.php public function testIndex () $ response = $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posts'); // getData () retourneert alle vars die aan het antwoord zijn gekoppeld. $ posts = $ response-> original-> getData () ['posts']; $ this-> assertInstanceOf ('Illuminate \ Database \ Eloquent \ Collection', $ posts); 

Met deze wijziging hebben we verklaard dat de controller moet voorbij lopen $ posts - een instantie van Verlichten \ Database \ Welsprekend \ Collection - naar het uitzicht. Uitstekend.


Bespotten van de database

Er is tot nu toe een in het oog springend probleem met onze tests. Heb je het gevangen?

Voor elke test wordt een SQL-query uitgevoerd in de database. Hoewel dit nuttig is voor bepaalde soorten testen (acceptatie, integratie), voor het testen van standaardcontrollers, zal dit alleen dienen om de prestaties te verminderen.

Ik heb dit nu meerdere keren in je schedel geboord. We zijn niet geïnteresseerd in het testen van de mogelijkheid van Eloquent om records uit een database op te halen. Het heeft zijn eigen testen. Taylor weet dat het werkt! Laten we geen tijd verspillen en verwerkingskracht die dezelfde tests herhalen.

In plaats daarvan is het het beste om de database te bespotten en alleen te verifiëren dat de juiste methoden worden aangeroepen met de juiste argumenten. Of, met andere woorden, we willen ervoor zorgen dat Bericht :: alle () schiet nooit en raakt de database. We weten dat dit werkt, dus het hoeft niet te worden getest.

Dit gedeelte is sterk afhankelijk van de Mockery-bibliotheek. Bekijk dat hoofdstuk uit mijn boek als je er nog niet bekend mee bent.

Vereiste Refactoring

Helaas hebben we tot nu toe de code zodanig gestructureerd dat het vrijwel onmogelijk is om deze te testen.

 # app / routes.php Route :: get ('posts', function () // Ouch. We kunnen dit niet testen !! $ posts = Post :: all (); return View :: make ('posts. index ') -> met (' berichten ', $ berichten););

Dit is precies waarom het als een slechte gewoonte wordt beschouwd om Eloquent-calls in uw controllers te nestelen. Verwar Laravel's gevels niet, die toetsbaar zijn en kunnen worden geruild met moppen (Queue :: shouldReceive ()), met uw Eloquent-modellen. De oplossing is om de databaselaag via de constructor in de controller te injecteren. Dit vereist wat refactoring.

Waarschuwing: Het opslaan van logica in route-callbacks is handig voor kleine projecten en API's, maar ze maken het testen ongelooflijk moeilijk. Gebruik controllers voor toepassingen van aanzienlijke omvang.

Laten we een nieuw hulpmiddel registreren door het te vervangen posts route met:

 # app / routes.php Route :: resource ('posts', 'PostsController');

... en creëer de nodige vindingrijke controller met Artisan.

 $ php artisan controller: maak PostsController Controller succesvol gemaakt!

Nu, in plaats van te verwijzen naar de Post model direct, we zullen het in de constructor van de controller injecteren. Hier is een beknopt voorbeeld dat alle rustgevende methoden weglaat, behalve degene die we momenteel graag willen testen.

 post = $ post;  public function index () $ posts = $ this-> post-> all (); return View :: make ('posts.index') -> with ('posts', $ posts); 

Houd er rekening mee dat het een beter idee is om een ​​interface te typen in plaats van te verwijzen naar het Eloquent-model zelf. Maar één ding tegelijk! Laten we dat doen.

Dit is een aanzienlijk betere manier om de code te structureren. Omdat het model nu wordt geïnjecteerd, hebben we de mogelijkheid om het uit te wisselen met een nepversie om te testen. Hier is een voorbeeld van precies dat doen:

 mock = Spot: :: mock ('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'); 

Het belangrijkste voordeel van deze herstructurering is dat de database nu nooit onnodig zal worden geraakt. In plaats daarvan, gebruiken we Mockery, we verifiëren alleen dat het allemaal methode wordt geactiveerd op het model.

 $ this-> mock -> shouldReceive ('all') -> once ();

Helaas, als u ervoor kiest om van codering af te zien in een interface, en in plaats daarvan de Post model in de controller, een beetje bedrog moet gebruikt worden om het gebruik van statica door Eloquent te omzeilen, wat kan botsen met Mockery. Dit is de reden waarom we zowel de Post en Welsprekend klassen in de constructor van de test, voordat de officiële versies zijn geladen. Op deze manier hebben we een schone lei om eventuele verwachtingen te bevestigen. Het nadeel is natuurlijk dat we geen standaardmethoden kunnen gebruiken, zoals het gebruik van Spotmodellen makePartial ().

De IoC-container

De IoC-container van Laravel vergemakkelijkt het proces van het injecteren van afhankelijkheden in uw klassen drastisch. Telkens wanneer een controller wordt aangevraagd, wordt deze uit de IoC-container verwijderd. Als zodanig, wanneer we moeten verklaren dat een schijnversie van Post moet worden gebruikt voor het testen, we hoeven Laravel alleen het voorbeeld van te geven Post dat zou gebruikt moeten worden.

 $ this-> app-> instance ('Post', $ this-> mock);

Beschouw deze code als te zeggen: "Hoi Laravel, wanneer je een instantie nodig hebt Post, Ik wil dat je mijn bespotte versie gebruikt."Omdat de app het Houder, we hebben direct toegang tot alle IoC-methoden.

Na het maken van de controller maakt Laravel gebruik van de kracht van PHP-reflectie om het type hint te lezen en de afhankelijkheid voor u in te spuiten. Dat is juist; je hoeft geen enkele binding te schrijven om dit toe te staan; het is geautomatiseerd!


omleidingen

Een andere veel voorkomende verwachting dat je jezelf zult schrijven is er een die ervoor zorgt dat de gebruiker wordt omgeleid naar de juiste locatie, misschien na het toevoegen van een nieuw bericht aan de database. Hoe kunnen we dit bereiken?

 # app / tests / controllers / PostsControllerTest.php public function testStore () $ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute ( 'posts.index'); 

Ervan uitgaande dat we een rustgevende smaak volgen, om een ​​nieuw bericht toe te voegen, zouden we dat doen POST naar de verzameling, of posts (verwar het niet POST aanvraag methode met de naam van de bron, die toevallig dezelfde naam heeft).

 $ this-> call ('POST', 'posts');

Dan hoeven we alleen maar gebruik te maken van een andere bewering van Laravel, assertRedirectedToRoute.

Tip: Wanneer een resource is geregistreerd bij Laravel (Route :: resource ()), registreert het raamwerk automatisch de noodzakelijke benoemde routes. Rennen php artisanale routes als je ooit vergeet wat deze namen zijn.

Misschien geeft u er ook de voorkeur aan om ervoor te zorgen dat de $ _POST superglobal wordt doorgegeven aan de creëren methode. Hoewel we niet fysiek een formulier indienen, kunnen we dit nog steeds toestaan ​​via de Input :: replace () methode, waarmee we deze array kunnen "stucken". Hier is de aangepaste test, die gebruik maakt van Mockery's met() methode om de argumenten te verifiëren die zijn doorgegeven aan de methode waarnaar wordt verwezen shouldReceive.

 # app / tests / controllers / PostsControllerTest.php public function testStore () Input :: replace ($ input = ['title' => 'Mijn titel']);

$ this-> mock -> shouldReceive ('create') -> once () -> with ($ input); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute ( 'posts.index');

Paths

Een ding dat we in deze test niet hebben overwogen, is validatie. Er moeten twee afzonderlijke paden door de op te slaan methode, afhankelijk van het feit of de validatie slaagt:

  1. Keer terug naar het formulier "Bericht maken" en geef de fouten in formuliervalidatie weer.
  2. Doorverwijzen naar de verzameling of de benoemde route, posts.index.

Als een best practice, moet elke test maar één pad door uw code vertegenwoordigen.

Dit eerste pad is voor mislukte validatie.

 # app / tests / controllers / PostsControllerTest.php public function testStoreFails () // Plaats instellen voor een mislukte validatie Input :: replace (['title' => "]); $ this-> app-> instance ('Bericht ', $ this-> mock); $ this-> call (' POST ',' posts '); // Failed validation moet het create-formulier $ this-> assertRedirectedToRoute (' posts.create ') opnieuw laden; // De fouten moet worden verzonden naar de weergave $ this-> assertSessionHasErrors (['title']);

Het bovenstaande codefragment verklaart expliciet welke fouten zouden moeten bestaan. Als alternatief kunt u het argument weglaten assertSessionHasErrors, in dat geval wordt alleen gecontroleerd of een berichtentas is geflitst (in vertaling is dit ook uw omleiding withErrors ($ fouten)).

Nu voor de test die succesvolle validatie afhandelt.

 # app / tests / controllers / PostsControllerTest.php public function testStoreSuccess () // Plaats voor succesvolle validatie instellen Input: replace (['title' => 'Foo Title']);

$ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); // Zou moeten doorsturen naar verzameling, met een succesflitsbericht $ this-> assertRedirectedToRoute ('posts.index', ['flash']);

De productiecode voor deze twee tests kan er als volgt uitzien:

 # app / controllers / PostsController.php public function store () $ input = Input :: all (); // We voeren validatie uit in de controller voor het gemak // Je zou dit naar het model moeten exporteren, of een service $ v = Validator :: make ($ input, ['title' => 'required']); if ($ v-> failed ()) return Redirect :: route ('posts.create') -> withInput () -> withErrors ($ v-> messages ());  $ this-> post-> create ($ input); return Redirect :: route ('posts.index') -> with ('flash', 'Uw bericht is aangemaakt!'); 

Let op hoe het Validator is genest direct in de controller? Over het algemeen raad ik u aan dit te abstraheren tot een service. Op die manier kunt u uw validatie afzonderlijk testen van controllers of routes. Laten we de dingen echter gewoon laten zoals ze zijn. Een ding om in gedachten te houden is dat we niet de spot drijven met Validator, hoewel je dat zeker zou kunnen doen. Omdat deze klas een façade is, kan deze eenvoudig worden geruild met een bespotte versie, via de gevel shouldReceive methode, zonder dat we ons zorgen hoeven te maken over het injecteren van een instantie via de constructor. Winnen!

 # app / controllers / PostsController.php Validator :: shouldReceive ('make') -> once () -> andReturn (Mockery :: mock (['failed' => 'true']));

Van tijd tot tijd zul je ontdekken dat een methode die moet worden bespot, een object zelf moet retourneren. Gelukkig is dit met Mockery een fluitje van een cent: we hoeven alleen maar een anonieme mock te maken en een array door te geven, die respectievelijk de methode-naam en responswaarde aangeeft. Als zodanig:

 Mockery :: mock (['fails' => 'true'])

bereidt een voorwerp voor dat a bevat mislukt () methode die terugkeert waar.


Vindplaatsen

Om optimale flexibiliteit mogelijk te maken, in plaats van een directe koppeling tussen uw controller en een ORM te maken, zoals Eloquent, is het beter om te coderen naar een interface. Het grote voordeel van deze aanpak is dat, als je misschien wel eens Welsprekend moet ruilen voor bijvoorbeeld Mongo of Redis, dit letterlijk de aanpassing van één enkele regel vereist. Sterker nog, de controller hoeft nooit te worden aangeraakt.

Repositories vertegenwoordigen de datatoegangslaag van uw applicatie.

Wat kan een interface voor het beheren van de databaselaag van een Post ziet eruit als? Dit zou u op weg moeten helpen.

  

Dit kan zeker worden uitgebreid, maar we hebben de minimale methoden voor de demo toegevoegd: allemaal, vind, en creëren. Merk op dat de repository-interfaces binnenin worden opgeslagen app / repositories. Omdat deze map standaard niet automatisch wordt geparoloaded, moeten we de composer.json bestand voor de toepassing om ernaar te verwijzen.

 // composer.json "autoload": "classmap": [// ... "app / repositories"]

Wanneer een nieuwe klasse aan deze map wordt toegevoegd, vergeet dan niet componist dump-autoload -o. De -O, (optimaliseren) vlag is optioneel, maar moet altijd worden gebruikt, als een beste methode.

Als je probeert deze interface in je controller te injecteren, grijpt Laravel je aan. Ga je gang; probeer het en zie. Dit is het aangepaste PostController, die is bijgewerkt om een ​​interface te injecteren in plaats van de Post Weldigend model.

 post = $ post;  public function index () $ posts = $ this-> post-> all (); return View :: make ('posts.index', ['posts' => $ posts]); 

Als u de server uitvoert en de uitvoer bekijkt, wordt u geconfronteerd met de gevreesde (maar mooie) Whoops-foutpagina, waarin wordt verklaard dat "PostRepositoryInterface is niet direct beschikbaar."


Als je erover nadenkt, is het kader natuurlijk kwetterend! Laravel is slim, maar het is geen mindreader. Er moet worden verteld welke implementatie van de interface binnen de controller moet worden gebruikt.

Laten we deze binding nu toevoegen aan app / routes.php. Later zullen we in plaats daarvan gebruikmaken van serviceproviders om dit soort logica op te slaan.

 # app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ EloquentPostRepository');

Verbaal deze functieaanroep als, "Laravel, schat, wanneer je een instantie nodig hebt PostRepositoryInterface, Ik wil dat je gebruikt EloquentPostRepository."

app / repositories / EloquentPostRepository zal simpelweg een verpakking zijn rond Eloquent die werkt PostRepositoryInterface. Op deze manier beperken we de API (en elke andere implementatie) niet tot de interpretatie van Eloquent; we kunnen de methoden een naam geven die we willen.

  

Sommigen beweren dat het Post model moet voor testbaarheid worden geïnjecteerd in deze implementatie. Als je het ermee eens bent, injecteer het dan gewoon via de constructor, normaal gesproken.

Dat is alles wat nodig is! Vernieuw de browser en alles moet weer normaal zijn. Alleen nu is uw applicatie veel beter gestructureerd en is de controller niet langer gekoppeld aan Eloquent.

Laten we ons voorstellen dat een paar maanden vanaf nu je baas je laat weten dat je Eloquent moet uitwisselen met Redis. Omdat u uw toepassing op deze toekomstbestendige manier hebt gestructureerd, hoeft u alleen de nieuwe te maken app / repositories / RedisPostRepository implementatie:

  

En update de binding:

 # app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ RedisPostRepository');

Direct benut je nu Redis in je controller. Merk op hoe app / controllers / PostsController.php werd nooit aangeraakt? Dat is het mooie ervan!


Structuur

Tot dusverre heeft onze organisatie het in deze les een beetje gemist. IoC-bindingen in de routes.php het dossier? Alle opslagplaatsen gegroepeerd in één map? Natuurlijk, dat werkt misschien in het begin, maar heel snel zal blijken dat dit niet schaalt.

In het laatste deel van dit artikel zullen we PSR-ify onze code gebruiken en gebruikmaken van serviceproviders om eventuele toepasselijke bindingen te registreren.

PSR-0 definieert de verplichte vereisten waaraan moet worden voldaan voor de interoperabiliteit van de autoloader.

Een lader PSR-0 kan via Composer worden geregistreerd bij Composer PSR-0 voorwerp.

 // composer.json "autoload": "psr-0": "Way": "app / lib /"

De syntaxis kan in het begin verwarrend zijn. Het was zeker voor mij. Een gemakkelijke manier om te ontcijferen "Way": "app / lib /" is om bij jezelf te denken, "De basismap voor de Manier naamruimte bevindt zich in app / lib."Vervang mijn achternaam natuurlijk door de naam van uw project. De directorystructuur die hieraan voldoet, zou zijn:

  • app /
    • lib /
    • Manier/

Vervolgens in plaats van alle repositories te groeperen in een repositories directory, een elegantere benadering zou kunnen zijn om ze in meerdere mappen te categoriseren, zoals:

  • app /
    • lib /
    • Manier/
      • opslag /
      • Post/
        • PostRepositoryInterface.php
        • EloquentPostRepository.php

Het is van vitaal belang dat we ons houden aan deze naamgeving en mapconventie, als we willen dat de autoloading werkt zoals verwacht. Het enige overblijvende ding om te doen is de namespaces voor updaten PostRepositoryInterface en EloquentPostRepository.

  

En voor de implementatie:

  

Daar gaan we; dat is veel schoner. Maar hoe zit het met die vervelende banden? Het routesbestand kan een handige plek zijn om te experimenteren, maar het heeft weinig zin om ze permanent op te slaan. In plaats daarvan gebruiken we serviceproviders.

Serviceproviders zijn niets meer dan bootstrap-klassen die kunnen worden gebruikt om alles te doen wat u maar wilt: een binding registreren, een afspraak maken, een routesbestand importeren, enz..

Een dienstverlener registreren() wordt automatisch door Laravel geactiveerd.

 app-> bind ('Way \ Storage \ Post \ PostRepositoryInterface', 'Way \ Storage \ Post \ EloquentPostRepository'); 

Om dit bestand bij Laravel bekend te maken, hoeft u het alleen maar in te voegen app / config / app.php, binnen de providers rangschikking.

 # app / config / app.php 'providers' => array ('Illuminate \ Foundation \ Providers \ ArtisanServiceProvider', 'Illuminate \ Auth \ AuthServiceProvider', // ... 'Way \ Storage \ StorageServiceProvider')

Goed; nu hebben we een speciaal bestand voor het registreren van nieuwe bindingen.

De tests bijwerken

Met onze nieuwe structuur op zijn plaats, in plaats van het Eloquent-model zelf te bespotten, kunnen we in plaats daarvan spotten PostRepositoryInterface. Hier is een voorbeeld van een dergelijke test:

 # app / tests / controllers / PostsControllerTest.php public function testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ Mock-> shouldReceive ( 'all') -> één keer (); $ this-> app-> instance ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posts'); 

We kunnen dit echter verbeteren. Het spreekt vanzelf dat elke methode erin PostsControllerTest vereist een bespotte versie van de repository. Als zodanig is het beter om een ​​deel van dit voorbereidende werk uit te pakken op zijn eigen manier, zoals:

 # app / tests / controllers / PostsControllerTest.php public function setUp () parent :: setUp (); $ This-> mock ( 'Way \ Storage \ bericht \ PostRepositoryInterface');  public function mock ($ class) $ mock = Mockery :: mock ($ class); $ this-> app-> instance ($ class, $ mock); return $ mock;  public function testIndex () $ this-> mock-> shouldReceive ('all') -> once (); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posts'); 

Niet slecht, ay?

Nu, als je supervlieg wilt zijn en bereid bent om een ​​vleugje testlogica toe te voegen aan je productiecode, kun je zelfs je spotternij uitvoeren binnen het Eloquent-model! Dit zou het volgende mogelijk maken:

 Bericht :: shouldReceive ( 'all') -> één keer ();

Achter de schermen zou dit bespotten PostRepositoryInterface, en update de IoC-binding. Je kunt niet veel leesbaarder worden dan dat!

Het toestaan ​​van deze syntaxis vereist alleen dat u de Post model, of beter, a