Testing Laravel Controllers

Testing controllers er ikke det enkleste i verden. Vel, la meg rephrase at: teste dem er en cinch; Det som er vanskelig, i det minste i begynnelsen, er å avgjøre hva å teste.

Skal en kontrollertest bekrefte tekst på siden? Skal det berøre databasen? Bør det sikre at variabler eksisterer i visningen? Hvis dette er din første hestetur, kan disse tingene være forvirrende! La meg hjelpe.

Controller tester bør verifisere svar, sørge for at de riktige databasetilgangsmetodene utløses, og hevder at de aktuelle instansvariablene sendes til visningen.

Prosessen med å teste en kontroller kan deles inn i tre stykker.

  • Isolere: Mock alle avhengigheter (kanskje unntatt Utsikt).
  • Anrop: Utløs ønsket kontrolleringsmetode.
  • Sørge for: Utfør påstand, verifisere at scenen er satt riktig.

Hello World of Controller Testing

Den beste måten å lære på disse tingene er gjennom eksempler. Her er "Hei Verden"av kontrollertesting i Laravel.

 klient-> forespørsel ('GET', 'innlegg'); 

Laravel utnytter en håndfull Symfony-komponenter for å lette prosessen med å teste ruter og visninger, inkludert HttpKernel, DomCrawler og BrowserKit. Det er derfor viktig at PHPUnit-testene dine arver fra, ikke PHPUnit \ _Framework \ _TestCase, men Testforsøk. Ikke bekymre deg, Laravel utvider det tidligere, men det hjelper med å sette opp Laravel-appen for testing, samt gir en rekke hjelpemåter som du oppfordres til å bruke. Mer om det snart.

I kodebiten ovenfor lager vi en forespørsel til / innlegg, eller localhost: 8000 / innlegg. Forutsatt at denne linjen legges til en ny installasjon av Laravel, vil Symfony kaste en NotFoundHttpException. Hvis du jobber sammen, kan du prøve det ved å løpe PHPUnit fra kommandolinjen.

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

I menneske-tale, dette betyr i hovedsak "Hei, jeg prøvde å ringe den ruten, men du har ikke noe registrert, lure!"

Som du kan forestille deg, er denne typen forespørsel vanlig nok til det punktet at det er fornuftig å gi en hjelpemetode, for eksempel $ Dette-> samtale (). Faktisk gjør Laravel det veldig! Dette betyr at det forrige eksempelet kan refactored, slik som:

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testIndex () $ this-> call ('GET', 'posts'); 

Overbelastning er din venn

Selv om vi holder fast med basalfunksjonaliteten i dette kapittelet, tar jeg ting i et steg videre ved å tillate slike metoder som $ Dette-> get (), $ Dette-> innlegget (), etc. Takket være PHP overbelastning krever dette bare tillegg av en enkelt metode, som du kan legge til app / tester / TestCase.php.

 # app / tester / TestCase.php offentlig funksjon __call ($ metode, $ args) if (in_array ($ metode, ['get', 'post', 'put', 'patch', 'delete']) returnere $ this-> call ($ metode, $ args [0]);  kaste nye BadMethodCallException; 

Nå er du fri til å skrive $ Dette-> get ( 'innlegg') og oppnå nøyaktig samme resultat som de to foregående eksemplene. Som nevnt ovenfor, la oss imidlertid holde fast med rammens basisfunksjonalitet for enkelhets skyld.

For å gjøre testen bestått, trenger vi bare å forberede den riktige ruten.

  

Løping PHPUnit igjen vil returnere oss til grønt.


Laravel's Helper Assertions

En test som du finner deg selv skriver gjentatte ganger, er en som sikrer at en kontroller overfører en bestemt variabel til en visning. For eksempel, index Metode av PostsController skal passere en $ innlegg variabel til tilhørende visning, ikke sant? På den måten kan visningen filtrere gjennom alle innlegg, og vise dem på siden. Dette er en viktig test for å skrive!

Hvis det er så vanlig en oppgave, ville det nok ikke være fornuftig for Laravel å gi en helper påstand for å oppnå dette? Selvfølgelig ville det. Og selvfølgelig gjør Laravel det!

Belyse \ Foundation \ Testing \ Testcase inkluderer en rekke metoder som vil redusere mengden koden som trengs for å utføre grunnleggende påstander drastisk. Denne listen inkluderer:

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

Følgende eksempler kaller GET / innlegg og bekrefter at visningene mottar variabelen, $ innlegg.

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testIndex () $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg'); 

Tips: Når det gjelder formatering, foretrekker jeg å gi en linjeskift mellom en tests påstand og koden som forbereder scenen.

assertViewHas er ganske enkelt litt sukker som inspiserer responsobjektet - som returneres fra $ Dette-> samtale () - og verifiserer at dataene knyttet til visningen inneholder en innlegg variabel.

Når du inspiserer responsobjektet, har du to kjernevalg.

  • $ Response-> getOriginalContent (): Hent originalt innhold, eller returnert Utsikt. Eventuelt kan du få tilgang til opprinnelig eiendom direkte, i stedet for å ringe getOriginalContent metode.
  • $ Response-> getContent (): Hent den gjengitte utgangen. Hvis en Utsikt Eksempel blir returnert fra ruten, da getContent () vil være lik HTML-utgangen. Dette kan være nyttig for DOM-verifikasjoner, for eksempel "visningen må inneholde denne strengen."

La oss anta at innlegg ruten består av:

  

Skal vi løpe PHPUnit, det vil skje med en nyttig neste steg budskap:

 1) PostsControllerTest :: testIndex Feilet hevdet at en matrise har de viktigste innleggene.

For å gjøre det grønt, henter vi bare innleggene og sender det til visningen.

 # app / routes.php Rute :: få ('innlegg', funksjon () $ innlegg = Post :: alle (); tilbake Vis :: lag ('posts.index', ['posts', $ posts]) ;);

En ting å huske på er at, som koden for tiden står, sikrer den bare at variabelen, $ innlegg, er sendt til visningen. Det inspiserer ikke verdien. De assertViewHas Valgfritt aksepterer et annet argument for å verifisere verdien av variabelen, så vel som dens eksistens.

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('innlegg', 'foo'); 

Med denne endrede koden har unles visningen en variabel, $ innlegg, det er lik foo, testen vil mislykkes. I denne situasjonen er det sannsynlig at vi heller ikke vil spesifisere en verdi, men i stedet erklære at verdien er en forekomst av Laravel s Belyse \ Database \ Veltalende \ Collection klasse. Hvordan kan vi oppnå det? PHPUnit gir en nyttig assertInstanceOf påstand for å fylle dette svært behovet!

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testIndex () $ response = $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg'); // getData () returnerer alle vars knyttet til svaret. $ posts = $ response-> original-> getData () ['posts']; $ this-> assertInstanceOf ('Illuminate \ Database \ Eloquent \ Collection', $ innlegg); 

Med denne modifikasjonen har vi erklært at kontrolleren passere $ innlegg - en forekomst av Belyse \ Database \ Veltalende \ Collection - til utsikten. Utmerket.


Mocking databasen

Det er et klart problem med våre tester så langt. Fikk du det?

For hver test utføres en SQL-spørring i databasen. Selv om dette er nyttig for visse typer testing (aksept, integrasjon), for grunnleggende kontrollerstesting, vil den bare tjene til å redusere ytelsen.

Jeg har boret dette inn i skallen din flere ganger på dette punktet. Vi er ikke interessert i å teste Eloquents evne til å hente poster fra en database. Den har egne tester. Taylor vet at det fungerer! La oss ikke kaste bort tid og prosessorkraft som gjentar de samme testene.

I stedet er det best å mocke databasen, og bare verifisere at de riktige metodene kalles med de riktige argumentene. Eller med andre ord, vi vil sikre det Innlegg :: alle () brenner aldri og treffer databasen. Vi vet at det fungerer, så det krever ikke testing.

Denne delen vil avhenge sterkt av Mockery-biblioteket. Vennligst prøv det kapitlet fra boken min, hvis du ikke er kjent med det.

Påkrevd Refactoring

Dessverre har vi hittil strukturert koden på en måte som gjør det praktisk talt umulig å teste.

 # app / routes.php Rute :: få ('innlegg', funksjon () // Ouch. Vi kan ikke teste dette! $ posts = Post :: all (); return View :: make ('posts. indeks ') -> med (' innlegg ', $ innlegg););

Dette er nettopp hvorfor det anses å være dårlig praksis for å neste Eloquent-samtaler til dine kontrollører. Ikke forveksle Laravel fasader, som er testbare og kan byttes ut med mocks (Kø :: shouldReceive ()), med dine Eloquent-modeller. Løsningen er å injisere databaselaget i kontrolleren gjennom konstruktøren. Dette krever noe refactoring.

Advarsel: Lagring av logikk innen rute tilbakekalling er nyttig for små prosjekter og APIer, men de gjør testingen utrolig vanskelig. For applikasjoner av en hvilken som helst betydelig størrelse, bruk kontroller.

La oss registrere en ny ressurs ved å erstatte innlegg rute med:

 # app / routes.php Rute :: ressurs ('innlegg', 'PostsController');

... og skape den nødvendige ressursrike kontrolleren med Artisan.

 $ php artisan controller: gjør PostsController Controller opprettet med hell!

Nå, snarere enn å referere til Post modell direkte, vil vi injisere den inn i kontrollerens konstruktør. Her er et kondensert eksempel som utelukker alle rolige metoder, bortsett fra den som vi for tiden er interessert i å teste.

 post = $ post;  offentlige funksjonsindeks () $ posts = $ this-> post-> all (); Return View :: make ('posts.index') -> med ('innlegg', $ innlegg); 

Vær oppmerksom på at det er en bedre ide å skrive et grensesnitt, i stedet for å referere til Eloquent-modellen, selv. Men en ting om gangen! La oss jobbe med det.

Dette er en betydelig bedre måte å strukturere koden på. Fordi modellen er injisert, har vi muligheten til å bytte ut den med en stødt versjon for testing. Her er et eksempel på å gjøre nettopp det:

 mock = Mockery :: mock ('Eloquent', 'Post');  offentlig funksjon tearDown () Mockery :: close ();  offentlig funksjon testIndex () $ this-> mock -> shouldReceive ('all') -> once () -> andReturn ('foo'); $ this-> app-> forekomst ('Post', $ this-> mock); $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg'); 

Nøkkelen til denne omstruktureringen er at databasen nå ikke vil bli truffet. I stedet bruker vi Mockery bare at alle Metoden utløses på modellen.

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

Dessverre, hvis du velger å avstå fra koding til et grensesnitt, og istedet injisere Post modell i kontrolleren, må litt trickery brukes for å komme seg rundt Eloquents bruk av statikk, som kan kollidere med Mockery. Det er derfor vi kaprer begge Post og Veltalende klasser i testens konstruktør, før de offisielle versjonene er lastet. På denne måten har vi et rent skifer for å erklære eventuelle forventninger. Ulempen er selvfølgelig at vi ikke kan standardisere noen eksisterende metoder, ved hjelp av Mockery-metoder, som makePartial ().

IoC Containeren

Laravel's IoC-beholder reduserer prosessen med å injisere avhengigheter i klassene dine drastisk. Hver gang en kontroller blir bedt om, blir den løst ut av IoC-beholderen. Som sådan, når vi trenger å erklære at en mock versjon av Post skal brukes til testing, trenger vi bare å gi Laravel med forekomsten av Post det bør brukes.

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

Tenk på denne koden som å si, "Hei Laravel, når du trenger en forekomst av Post, Jeg vil at du skal bruke min spottede versjon."Fordi appen strekker seg ut Container, Vi har tilgang til alle IoC-metoder direkte ut av det.

Ved instilling av kontrolleren utnytter Laravel kraften til PHP refleksjon for å lese typehint og injisere avhengigheten av deg. Det er riktig; du trenger ikke å skrive en enkelt bindende for å tillate dette; det er automatisert!


omadresseringer

En annen vanlig forventning om at du finner deg selv å skrive er en som sikrer at brukeren blir omdirigert til riktig sted, kanskje ved å legge til et nytt innlegg i databasen. Hvordan kan vi oppnå dette?

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testStore () $ this-> mock -> shouldReceive ('create') -> once (); $ this-> app-> forekomst ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ Dette-> assertRedirectedToRoute ( 'posts.index'); 

Forutsatt at vi følger en avslappende smak, for å legge til et nytt innlegg, ville vi POST til samlingen, eller innlegg (ikke forveksle POST forespørselsmetode med ressursnavnet, som bare skjer med samme navn).

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

Da trenger vi bare å utnytte en annen av Laravels hjelperegler, assertRedirectedToRoute.

Tips: Når en ressurs er registrert hos Laravel (Route :: ressurs ()), vil rammen automatisk registrere de nødvendige navngitte rutene. Løpe php artisan ruter hvis du noen gang glemmer hva disse navnene er.

Du kan også foretrekke å også sørge for at $ _POST superglobal er passert til skape metode. Selv om vi ikke fysisk sender inn et skjema, kan vi likevel tillate dette, via Input :: erstatte () metode, som gjør at vi kan "stubbe" dette systemet. Her er den modifiserte testen, som bruker Mockery's med() metode for å verifisere argumentene som er overført til metoden referert av shouldReceive.

 # app / tester / kontroller / PostsControllerTest.php public function testStore () Input :: erstatte ($ input = ['title' => 'My Title']);

$ this-> mock -> shouldReceive ('create') -> en gang () -> med ($ input); $ this-> app-> forekomst ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ Dette-> assertRedirectedToRoute ( 'posts.index');

Paths

En ting vi ikke har vurdert i denne testen, er validering. Det bør være to separate stier gjennom butikk metode, avhengig av om validering passerer:

  1. Omdiriger tilbake til skjemaet "Opprett innlegg", og vis skjema validering feil.
  2. Viderekobling til samlingen, eller den navngitte ruten, posts.index.

Som en best practice bør hver test representere en vei gjennom koden din.

Denne første banen vil være for mislykket validering.

 # app / tester / kontroller / PostsControllerTest.php public function testStoreFails () // Angi stadium for en mislykket validering Input :: erstatt (['title' => "]); $ this-> app-> instance ', $ this-> mock); $ this-> call (' POST ',' posts '); // Mislykket validering skal laste inn opprettingsskjemaet $ this-> assertRedirectedToRoute (' posts.create '); // Feilene skal sendes til visningen $ this-> assertSessionHasErrors (['title']);

Kodestykket ovenfor angir eksplisitt hvilke feil som skal finnes. Alternativt kan du utelate argumentet til assertSessionHasErrors, I så fall vil det bare verifisere at en meldingspose har blitt blinket (i oversettelse, inkluderer omadressen din withErrors ($ feil)).

Nå for testen som håndterer vellykket validering.

 # app / tester / kontroller / PostsControllerTest.php public function testStoreSuccess () // Sett stadium for vellykket validering Input :: erstatte (['title' => 'Foo Title']);

$ this-> mock -> shouldReceive ('create') -> en gang (); $ this-> app-> forekomst ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); // Skal omdirigere til samling, med en vellykket flashmelding $ this-> assertRedirectedToRoute ('posts.index', ['flash']);

Produksjonskoden for disse to testene kan se ut som:

 # app / controllers / PostsController.php offentlig funksjon butikk () $ input = Input :: all (); // Vi kjører validering i kontrolleren for bekvemmelighet // Du burde eksportere dette til modellen, eller en tjeneste $ v = Validator :: make ($ input, ['title' => 'required']); hvis ($ v-> mislykkes ()) return Redirect :: rute ('posts.create') -> withInput () -> withErrors ($ v-> messages ());  $ this-> post-> create ($ input); returnere Omdirigering :: rute ('posts.index') -> med ('flash', 'Ditt innlegg har blitt opprettet!'); 

Legg merke til hvordan Validator er nestet direkte i kontrolleren? Generelt vil jeg anbefale at du abstrakt dette bort til en tjeneste. På den måten kan du teste din validering i isolasjon fra noen kontroller eller ruter. Likevel, la oss forlate ting som de er for enkelhets skyld. En ting å huske på er at vi ikke mocking the Validator, selv om du sikkert kunne gjøre det. Fordi denne klassen er en fasade, kan den enkelt byttes ut med en mocked versjon, via fasaden shouldReceive metode uten at vi trenger å bekymre oss for å injisere en forekomst gjennom konstruktøren. Vinne!

 # app / controllers / PostsController.php Validator :: shouldReceive ('make') -> en gang () -> ogReturn (Mockery :: mock (['fails' => 'true']));

Fra tid til annen vil du oppdage at en metode som må hånes, skal returnere et objekt, selv. Heldigvis, med Mockery, er dette et stykke kake: vi trenger bare å lage en anonym mock, og passere en matrise, som signerer metodens navn og responstid. Som sådan:

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

vil forberede et objekt som inneholder en mislykkes () metode som returnerer ekte.


repositories

For å gi optimal fleksibilitet, i stedet for å skape en direkte kobling mellom kontrolleren og en ORM, som Eloquent, er det bedre å kode til et grensesnitt. Den betydelige fordelen med denne tilnærmingen er at hvis du kanskje må bytte ut Eloquent for, si, Mongo eller Redis, gjør det bokstavelig talt en modifisering av en enkelt linje. Enda bedre trenger kontrolleren aldri å bli rørt.

Repositories representerer data tilgang lag av søknaden din.

Hva kan et grensesnitt for å administrere databaselaget av a Post ser ut som? Dette burde komme i gang.

  

Dette kan sikkert bli utvidet, men vi har lagt til de minste minimumsmetoder for demoen: alle, finne, og skape. Legg merke til at lagringsgrensesnittene lagres innenfor app / repositories. Fordi denne mappen ikke er autoloaded som standard, må vi oppdatere composer.json fil for søknaden for å referere den.

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

Når en ny klasse legges til denne katalogen, ikke glem å komponist dump-autoload -o. De -o, (optimalisere) flagg er valgfritt, men skal alltid brukes som en god praksis.

Hvis du prøver å injisere dette grensesnittet i kontrolleren din, vil Laravel snappe på deg. Gå videre; prøv det og se. Her er den endrede PostController, som har blitt oppdatert for å injisere et grensesnitt, i stedet for Post Eloquent-modell.

 post = $ post;  offentlige funksjonsindeks () $ posts = $ this-> post-> all (); returnere Vis :: lage ('posts.index', ['posts' => $ posts]); 

Hvis du kjører serveren og ser utgangen, blir du møtt av den fryktede (men vakre) Whoops-feilsiden, som erklærer at "PostRepositoryInterface er ikke instantiable."


Hvis du tenker på det, er det selvsagt rammen som skjer! Laravel er smart, men det er ikke en sinneleser. Det må bli fortalt hvilken implementering av grensesnittet som skal brukes i kontrolleren.

For nå, la oss legge til dette bindende til app / routes.php. Senere vil vi isteden bruke tjenesteleverandører til å lagre denne typen logikk.

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

Verbalize denne funksjonen samt, "Laravel, baby, når du trenger en forekomst av PostRepositoryInterface, Jeg vil at du skal bruke EloquentPostRepository."

app / repositories / EloquentPostRepository vil ganske enkelt være en wrapper rundt Eloquent som implementerer PostRepositoryInterface. På den måten begrenser vi ikke API-en (og alle andre implementeringer) til Eloquents tolkning. Vi kan nevne metodene vi ønsker.

  

Noen kan hevde at Post modellen skal injiseres i denne implementeringen for testbarhetsformål. Hvis du er enig, bare injiser den gjennom konstruktøren, per vanlig.

Det er alt det bør ta! Oppdater nettleseren, og ting skal være tilbake til det normale. Bare, nå er søknaden din langt bedre strukturert, og kontrolleren er ikke lenger koblet til Eloquent.

La oss forestille oss at noen få måneder fra nå informerer sjefen deg om at du må bytte Eloquent ut med Redis. Vel, fordi du har strukturert søknaden din på denne fremtidssikre måten, trenger du bare å opprette den nye app / repositories / RedisPostRepository gjennomføring:

  

Og oppdater bindingen:

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

Straks bruker du Redis i din kontroller. Legg merke til hvordan app / kontrollere / PostsController.php ble aldri rørt? Det er skjønnheten i det!


Struktur

Hittil i denne leksjonen har organisasjonen vår vært litt mangelfull. IoC bindinger i routes.php fil? Alle repositorier gruppert sammen i en katalog? Visst, det kan fungere i begynnelsen, men veldig raskt vil det bli tydelig at dette ikke skaleres.

I den siste delen av denne artikkelen vil vi PSR-ify vår kode, og utnytte tjenesteleverandører til å registrere eventuelle bindinger.

PSR-0 definerer de obligatoriske kravene som må overholdes for autoloader interoperabilitet.

En PSR-0-laster kan være registrert hos Composer, via PSR-0 gjenstand.

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

Syntaxen kan forveksles først. Det var sikkert for meg. En enkel måte å tyde på "Vei": "app / lib /" er å tenke på deg selv, "Basemappen for Vei namespace er plassert i app / lib."Selvfølgelig, erstatt etternavnet mitt med navnet på prosjektet. Katalogstrukturen for å matche dette ville være:

  • app /
    • lib /
    • Vei/

Neste, i stedet for å gruppere alle lagre i en repositories katalog, en mer elegant tilnærming kan være å kategorisere dem i flere kataloger, slik som:

  • app /
    • lib /
    • Vei/
      • Oppbevaring/
      • Post/
        • PostRepositoryInterface.php
        • EloquentPostRepository.php

Det er viktig at vi overholder denne navngivnings- og mappekonvensjonen, hvis vi vil at autoloading skal fungere som forventet. Det eneste gjenværende å gjøre er å oppdatere navneområdene for PostRepositoryInterface og EloquentPostRepository.

  

Og for gjennomføringen:

  

Der går vi; det er mye renere. Men hva med de irriterende bindingene? Rutenes fil kan være et passende sted å eksperimentere, men det er ikke fornuftig å lagre dem der permanent. I stedet bruker vi tjenesteleverandører.

Tjenesteleverandører er ikke mer enn oppstartsklasser som kan brukes til å gjøre alt du ønsker: registrere en binding, koble til en hendelse, importere en rutefil, etc.

En tjenesteleverandør registrere() vil bli utløst automatisk av Laravel.

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

For å gjøre denne filen kjent med Laravel, trenger du bare å inkludere den app / config / app.php, innen tilbydere matrise.

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

Flink; nå har vi en dedikert fil for registrering av nye bindinger.

Oppdatering av testene

Med vår nye struktur på plass, i stedet for å spotte Eloquent-modellen, kan vi i stedet spotte PostRepositoryInterface. Her er et eksempel på en slik test:

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ Blindprøve-> shouldReceive ( 'alle') -> én gang (); $ this-> app-> forekomst ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg'); 

Vi kan imidlertid forbedre dette. Det står til grunn at alle metoder innenfor PostsControllerTest vil kreve en mocked versjon av depotet. Som sådan er det bedre å trekke ut noe av dette prep-arbeidet i sin egen metode, slik som:

 # app / tester / kontroller / PostsControllerTest.php offentlig funksjon setUp () foreldre :: setUp (); $ Dette-> mock ( 'Way \ Storage \ Post \ PostRepositoryInterface');  offentlig funksjon mock ($ klasse) $ mock = Mockery :: mock ($ class); $ this-> app-> forekomst ($ class, $ mock); returner $ mock;  offentlig funksjon testIndex () $ this-> mock-> shouldReceive ('all') -> once (); $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg'); 

Ikke dårlig, ay?

Nå, hvis du vil være super-fly, og er villig til å legge til en touch av testlogikk til produksjonskoden din, kan du til og med utføre din mocking innen Eloquent-modellen! Dette ville tillate:

 Innlegg :: shouldReceive ( 'alle') -> én gang ();

Bak kulissene, ville dette gjemme seg PostRepositoryInterface, og oppdater IoC-bindingen. Du kan ikke bli mye mer lesbar enn det!

Å tillate for denne syntaksen krever bare at du oppdaterer Post modell, eller bedre, a Basismodellen at alle Eloquent-modellene strekker seg. Her er et eksempel på den tidligere:

  

Hvis du klarer det indre "Skal jeg legge inn testlogikk i produksjonskode"kamp, ​​vil du oppdage at dette muliggjør betydelig mer lesbare tester.

 en gang(); $ this-> call ('GET', 'posts'); $ Dette-> assertViewHas ( 'innlegg');  offentlig funksjon testStoreFails () Input :: erstatt ($ input = ['title' => "]); $ this-> call ('POST', 'innlegg'); $ this-> assertRedirectedToRoute ('posts.create '); $ this-> assertSessionHasErrors (); offentlig funksjonstestStoreSuccess () Input :: erstatte ($ input = [' title '=>' Foo Title ']); Post :: shouldReceive (' create ') -> en gang (); $ this-> call ('POST', 'innlegg'); $ this-> assertRedirectedToRoute ('posts.index', ['flash']);

Det føles bra, ikke sant? Forhåpentligvis har denne artikkelen ikke vært for overveldende. Nøkkelen er å lære å organisere lagerene dine på en slik måte at de blir så enkle som mulig å mocke og injisere inn i dine kontroller. Som et resultat av denne innsatsen vil testene dine bli lynrask!

Denne artikkelen er et utdrag fra min kommende bok, Laravel Testing Decoded. Hold deg oppdatert for utgivelsen i mai 2013!