Testing controllers är inte det enklaste i världen. Tja, låt mig omformulera det: att testa dem är en cinch; Det som är svårt, åtminstone först, är att bestämma Vad att testa.
Ska ett kontrollerstest verifiera texten på sidan? Ska det röra databasen? Ska det se till att variabler finns i vyn? Om det här är din första hästtur kan dessa saker vara förvirrande! Låt mig hjälpa.
Controller tester bör verifiera svar, se till att de korrekta databasåtkomstmetoderna utlöses och hävda att lämpliga instansvariabler skickas till vyn.
Processen med att testa en kontroller kan delas in i tre delar.
Se
).Det bästa sättet att lära sig dessa saker är genom exempel. Här är "Hej världen"av kontrollerstestning i Laravel.
klient-> förfrågan ('GET', 'inlägg');
Laravel använder en handfull Symfony-komponenter för att underlätta processen att testa rutter och visningar, inklusive HttpKernel, DomCrawler och BrowserKit. Det är därför det är av största vikt att dina PHPUnit-tester ärva från, inte PHPUnit \ _Framework \ _TestCase
, men Testfall
. Oroa dig inte, Laravel förlänger fortfarande den förra, men det hjälper till att konfigurera Laravel-appen för testning, samt ger en mängd olika hjälprövningsmetoder som du uppmanas att använda. Mer om det inom kort.
I kodfältet ovan gör vi en SKAFFA SIG
begära att / inlägg
, eller localhost: 8000 / inlägg
. Förutsatt att denna rad läggs till i en ny installation av Laravel, kommer Symfony att kasta en NotFoundHttpException
. Om du arbetar tillsammans, prova det genom att springa PHPUnit
från kommandoraden.
$ phpunit 1) PostsControllerTest :: testIndex Symfony \ Component \ HttpKernel \ Undantag \ NotFoundHttpException:
I människa-speak, detta betyder i huvudsak "Hej, jag försökte ringa den vägen, men du har inget registrerat, lur!"
Som du kan föreställa dig, är denna typ av förfrågan vanligt så att det är vettigt att tillhandahålla en hjälparmetod, till exempel $ This-> samtal ()
. Faktum är att Laravel gör så mycket! Det betyder att det föregående exemplet kan refactoreras, som så:
# app / test / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts');
Även om vi klarar av basfunktionaliteten i det här kapitlet tar jag saker i ett steg längre genom att möjliggöra sådana metoder som $ This-> get ()
, $ This-> post ()
, etc. Tack vare PHP överbelastning kräver detta bara en enda metod, som du kan lägga till app / test / TestCase.php
.
# app / test / TestCase.php public function __call ($ metod, $ args) if (in_array ($ metod, ['få', 'post', 'put', 'patch', 'delete']) returnera $ this-> call ($ method, $ args [0]); kasta ny BadMethodCallException;
Nu är du fri att skriva $ This-> get ( 'inlägg')
och uppnå exakt samma resultat som de föregående två exemplen. Såsom nämnts ovan, låt oss dock hålla fast vid ramens basfunktionalitet för enkelhetens skull.
För att klara provet behöver vi bara förbereda rätt väg.
Löpning
PHPUnit
återigen kommer vi tillbaka till grönt.
Laravel's Helper Assertions
Ett test som du själv kommer att skriva flera gånger är en som säkerställer att en kontroller överför en viss variabel till en vy. Till exempel,
index
metod avPostsController
ska passera en$ inlägg
variabel till dess associerade vy, eller hur? På så sätt kan visningen filtrera genom alla inlägg och visa dem på sidan. Detta är ett viktigt test att skriva!Om det är så vanligt en uppgift, skulle det än en gång inte vara meningsfullt för Laravel att ge en hjälprövning för att uppnå denna sak? Självklart skulle det. Och självklart gör Laravel det!
Illuminate \ Foundation \ Testing \ testfall
innehåller ett antal metoder som drastiskt minskar mängden kod som behövs för att utföra grundläggande påståenden. Denna lista innehåller:
assertViewHas
assertResponseOk
assertRedirectedTo
assertRedirectedToRoute
assertRedirectedToAction
assertSessionHas
assertSessionHasErrors
Följande exempel ringer GET / inlägg
och verifierar att dess åsikter får variabeln, $ inlägg
.
# app / test / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg');
Tips: När det gäller formatering föredrar jag att ge en radbrytning mellan ett tests påstående och koden som förbereder scenen.
assertViewHas
är helt enkelt en bit av socker som inspekterar responsobjektet - vilket returneras från $ This-> samtal ()
- och verifierar att de data som är associerade med vyn innehåller en inlägg
variabel.
När du inspekterar svarobjektet har du två kärnval.
$ Responsen> getOriginalContent ()
: Hämta originalinnehållet eller returnerat Se
. Eventuellt kan du komma åt original-
egendom direkt, snarare än att ringa getOriginalContent
metod.$ Responsen> getContent ()
: Hämta den återgivna utsignalen. Om en Se
Exempel återkommer från rutten, då getContent ()
kommer att vara lika med HTML-utgången. Detta kan vara till hjälp för DOM-verifieringar, till exempel "vyn måste innehålla den här strängen."Låt oss anta att inlägg
rutt består av:
Ska vi springa
PHPUnit
, det kommer att squawk med en hjälpsam Nästa steg meddelande:1) PostsControllerTest :: testIndex Misslyckades hävdar att en matris har de viktigaste inläggen.För att göra den grön, hämtar vi bara inläggen och skickar den till vyn.
# app / routes.php Rutt :: få ('inlägg', funktion () $ inlägg = Inlägg :: alla (); Retur Visa :: Gör ('posts.index', ['posts', $ posts]) ;);En sak att komma ihåg är att, som koden för närvarande står, säkerställer den bara att variabeln,
$ inlägg
, skickas till vyn. Det inspekterar inte sitt värde. DeassertViewHas
accepterar eventuellt ett andra argument för att verifiera värdet av variabeln, såväl som dess existens.# app / test / controllers / PostsControllerTest.php public function testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ('inlägg', 'foo');Med den här modifierade koden har unles visningen en variabel,
$ inlägg
, det är lika medfoo
, testet kommer att misslyckas. I det här fallet är det troligt att vi hellre inte vill ange ett värde, utan att förklara att värdet är en förekomst av Laravel sIlluminate \ Database \ Eloquent \ Collection
klass. Hur kan vi uppnå det? PHPUnit ger ett bra hjälpmedelassertInstanceOf
påstående att fylla detta mycket behov!# app / test / kontroller / PostsControllerTest.php public function testIndex () $ response = $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg'); // getData () returnerar alla vars bifogade svaret. $ posts = $ response-> original-> getData () ['posts']; $ this-> assertInstanceOf ('Illuminate \ Database \ Eloquent \ Collection', $ inlägg);Med denna ändring har vi förklarat att regulatorn måste passera
$ inlägg
- en förekomst avIlluminate \ Database \ Eloquent \ Collection
- till vyn. Excellent.
Mocking databasen
Det finns ett brutalt problem med våra test hittills. Fångade du den?
För varje test utförs en SQL-fråga i databasen. Även om det här är användbart för vissa typer av testning (acceptans, integration), för grundläggande kontroller av kontroller, kommer den bara att fungera för att minska prestanda.
Jag har borrat det här i din skalle flera gånger vid denna tidpunkt. Vi är inte intresserade av att testa Eloquents förmåga att hämta poster från en databas. Den har sina egna tester. Taylor vet att det fungerar! Låt oss inte slösa tid och bearbetningskraft som upprepar samma test.
Istället är det bäst att mocka databasen och bara verifiera att lämpliga metoder kallas med de rätta argumenten. Eller med andra ord vill vi se till att
Post :: alla ()
brinner aldrig och träffar databasen. Vi vet att det fungerar, så det behöver inte testas.Det här avsnittet kommer att bero mycket på Mockery-biblioteket. Läs igenom det här kapitlet från min bok om du inte är bekant med den.
Obligatorisk Refactoring
Tyvärr har vi hittills strukturerat koden på ett sätt som gör det praktiskt taget omöjligt att testa.
# app / routes.php Rutt :: få ("inlägg", funktion () // Ouch. Vi kan inte testa detta !! $ posts = Post :: all (); returnera Visa :: make ('posts. index ') -> med (' inlägg ', $ inlägg););Det är just därför det anses vara dålig praxis att neka Eloquent-samtal till dina kontroller. Förvirra inte Laravel fasader, som är testbara och kan bytas ut med mocks (
Kö :: shouldReceive ()
), med dina Eloquent-modeller. Lösningen är att injicera databasskiktet i regulatorn genom konstruktören. Detta kräver viss refactoring.Varning: Lagring av logik inom ruttbackbacks är användbart för små projekt och API, men de gör testningen oerhört svårt. För applikationer av någon större storlek, använd kontroller.
Låt oss registrera en ny resurs genom att ersätta
inlägg
rutt med:# app / routes.php Rutt :: resurs ('inlägg', 'PostsController');... och skapa den nödvändiga resursfulla kontrollanten med Artisan.
$ php artisan controller: gör PostsController Controller skapad framgångsrikt!Nu, snarare än att hänvisa till
Posta
modell direkt, injicerar vi den i regulatorns konstruktör. Här är ett kondenserat exempel som utesluter alla vilsamma metoder utom den som vi för närvarande är intresserade av att testa.post = $ post; offentliga funktionsindex () $ posts = $ this-> post-> all (); returnera Visa :: make ('posts.index') -> med ('inlägg', $ inlägg);Observera att det är en bättre idé att skriva ett gränssnitt, istället för att referera till Eloquent-modellen, själv. Men en sak i taget! Låt oss arbeta för det.
Detta är ett betydligt bättre sätt att strukturera koden. Eftersom modellen nu injiceras har vi möjlighet att byta ut det med en provad version för testning. Här är ett exempel på att göra just det:
mock = Mockery :: mock ('Eloquent', 'Post'); allmän funktion tearDown () Mockery :: close (); public function testIndex () $ this-> mock -> shouldReceive ('all') -> en gång () -> ochReturn ('foo'); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg');Nyckeln till denna omstrukturering är att databasen nu aldrig kommer att bli slagen. Istället använder vi Mockery bara att
Allt
Metoden utlöses på modellen.$ this-> mock -> shouldReceive ('all') -> once ();Tyvärr, om du väljer att avstå från kodning till ett gränssnitt, och istället injicera
Posta
modell i regulatorn, måste lite trickery användas för att ta sig runt Eloquents användning av statik, som kan kollidera med Mockery. Det är därför vi kapar bådePosta
ochVältalig
klasser inom testets konstruktör, innan de officiella versionerna har laddats. På så sätt har vi en ren skiffer för att förklara några förväntningar. Nackdelen är givetvis att vi inte kan standardisera några befintliga metoder, genom att använda Mockery-metoder, sommakePartial ()
.IoC Container
Laravels IoC-behållare underlättar processen för att injicera beroenden i dina klasser drastiskt. Varje gång en kontroller begärs löses den ur IoC-behållaren. Som sådan, när vi måste förklara det som en mock version av
Posta
bör användas för testning, behöver vi bara ge Laravel en förekomst avPosta
det ska användas.$ this-> app-> instance ('Post', $ this-> mock);Tänk på den här koden som säger "Hej Laravel, när du behöver en förekomst av
Posta
, Jag vill att du ska använda min mocked version."Eftersom appen sträcker sigBehållare
, Vi har tillgång till alla IoC-metoder direkt av det.Efter instabilisering av kontrollenheten utnyttjar Laravel kraften i PHP-reflektion för att läsa typtypen och injicera beroendet för dig. Det är rätt; du behöver inte skriva en enda bindning för att tillåta detta; det är automatiserat!
omdirigeringar
En annan vanlig förhoppning om att du hittar dig själv skriver är en som säkerställer att användaren omdirigeras till rätt plats, kanske efter att du lägger till ett nytt inlägg i databasen. Hur kan vi uppnå detta?
# app / test / controllers / PostsControllerTest.php public function testStore () $ this-> mock -> shouldReceive ('create') -> en gång (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute (posts.index ');Förutsatt att vi följer en vilsam smak, för att lägga till ett nytt inlägg, skulle vi
POSTA
till samlingen, ellerinlägg
(förväxla intePOSTA
begäran metod med resursnamnet, som bara råkar ha samma namn).$ this-> call ('POST', 'posts');Då behöver vi bara utnyttja Larviks hjälpresatser,
assertRedirectedToRoute
.Tips: När en resurs är registrerad hos Laravel (
Rutt :: resurs ()
), kommer ramverket automatiskt att registrera de nödvändiga namngivna rutterna. Springaphp artisan rutter
om du någonsin glömmer vad dessa namn är.Du kanske föredrar att också se till att
$ _POST
superglobal skickas tillskapa
metod. Även om vi inte fysiskt skickar in ett formulär kan vi fortfarande tillåta detta viaInput :: replace ()
metod som tillåter oss att "stubba" denna array. Här är det modifierade testet, som använder Mockery'smed()
Metod för att verifiera argumenten som överförts till den metod som refereras avshouldReceive
.# app / test / controllers / PostsControllerTest.php public function testStore () Input :: ersätt ($ input = ['title' => 'My Title']); $ this-> mock -> shouldReceive ('create') -> en gång () -> med ($ input); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute (posts.index ');
Paths
En sak som vi inte har beaktat i detta test är validering. Det borde finnas två separata vägar genom
Lagra
metod beroende på om valideringen passerar:
- Vidarekoppla tillbaka till formuläret "Skapa inlägg" och visa formulärvalideringsfel.
- Vidarekoppla till samlingen eller den angivna rutten,
posts.index
.Som bästa praxis ska varje test representera en enda väg genom din kod.
Den här första sökvägen kommer att vara för felaktig validering.
# app / test / controllers / PostsControllerTest.php public function testStoreFails () // Ange steg för en misslyckad validering Input :: ersätt (['title' => "]); $ this-> app-> instance ', $ this-> mock); $ this-> call (' POST ',' posts '); // Felaktig validering bör ladda om formuläret $ this-> assertRedirectedToRoute (' posts.create '); // Felen ska skickas till vyn $ this-> assertSessionHasErrors (['title']);Kodestycket ovan förklarar uttryckligen vilka fel som ska finnas. Alternativt kan du utelämna argumentet till
assertSessionHasErrors
, i vilket fall det bara kommer att verifiera att en meddelandesäck har blinkats (i översättning inkluderar din omdirigeringwithErrors ($ fel)
).Nu för testet som hanterar framgångsrik validering.
# app / test / controllers / PostsControllerTest.php public function testStoreSuccess () // Ange scen för framgångsrik validering Input :: ersätt (['title' => 'Foo Title']); $ this-> mock -> shouldReceive ('create') -> en gång (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); // Ska omdirigera till samling, med ett snabbmeddelande $ this-> assertRedirectedToRoute ('posts.index', ['flash']);Produktionskoden för dessa två test kan se ut som:
# app / controllers / PostsController.php public function store () $ input = Input :: alla (); // Vi kommer att köra validering i kontrollenheten för bekvämlighet // Du ska exportera detta till modellen, eller en tjänst $ v = Validator :: make ($ input, ['title' => 'required']); om ($ v-> misslyckas ()) return Redirect :: route ('posts.create') -> med Input () -> withErrors ($ v-> messages ()); $ this-> post-> skapa ($ input); returnera Omdirigering :: rutt ('posts.index') -> med ('flash', 'Ditt inlägg har skapats!');Lägg märke till hur
validator
nestas direkt i kontrollenheten? I allmänhet rekommenderar jag att du abstraherar detta bort till en tjänst. På så sätt kan du testa din validering i isolering från alla kontroller eller rutter. Ändå låt oss lämna saker som de är för enkelhetens skull. En sak att komma ihåg är att vi inte mocking thevalidator
, även om du verkligen kunde göra det. Eftersom den här klassen är en fasad kan den enkelt bytas ut med en mockad version, via fasadenshouldReceive
metod, utan att vi behöver oroa oss för att injicera en instans genom konstruktören. Vinna!# app / controllers / PostsController.php Validator :: shouldReceive ('make') -> en gång () -> ochReturn (Mockery :: mock (['fails' => 'true']));Från tid till annan kommer du att upptäcka att en metod som måste bespottas ska returnera ett objekt, självt. Lyckligtvis, med Mockery, är detta en bit kaka: vi behöver bara skapa en anonym mock, och passera en matris, som signalerar metodnamn respektive responstid. Som sådan:
Mockery :: mock (['fails' => 'true'])kommer att förbereda ett objekt som innehåller en
misslyckas ()
metod som returnerarSann
.
arkiv
För att möjliggöra optimal flexibilitet, snarare än att skapa en direktlänk mellan din controller och en ORM, som Eloquent, är det bättre att koda till ett gränssnitt. Den stora fördelen med detta tillvägagångssätt är att om du kanske behöver byta ut Eloquent för, säg, Mongo eller Redis, gör det så att bokstavligen kräver modifiering av en enda rad. Ännu bättre behöver regulatorn aldrig bli rörd.
Repositories representerar dataåtkomstskiktet i din ansökan.
Vad kan ett gränssnitt för hantering av databaslagret av a
Posta
ser ut som? Detta borde komma igång.Det kan säkert förlängas, men vi har lagt till de minsta minimimetoderna för demo:
Allt
,hitta
, ochskapa
. Observera att förvarets gränssnitt lagras inomapp / förråd
. Eftersom den här mappen inte är autoloaded som standard måste vi uppdateracomposer.json
fil för ansökan att hänvisa till det.// composer.json "autoload": "classmap": [// ... "app / repositories"]När en ny klass läggs till i katalogen, glöm inte att
kompositör dump-autoload -o
. De-o
, (optimera) flagg är frivilligt, men bör alltid användas som bästa praxis.Om du försöker injicera det här gränssnittet i din styrenhet, kommer Laravel att snäppa till dig. Varsågod; prova och se. Här är den modifierade
Post
, som har uppdaterats för att injicera ett gränssnitt, snarare änPosta
Eloquent-modell.post = $ post; offentliga funktionsindex () $ posts = $ this-> post-> all (); returnera Visa :: make ('posts.index', ['posts' => $ inlägg]);Om du kör servern och ser utmatningen kommer du att mötas med den fruktade (men vackra) Whoops-felsidan, som förklarar att "PostRepositoryInterface är inte instansbar."
Om du funderar på det, är det självklart att ramarna är squawking! Laravel är smart, men det är inte en läsare. Det måste höras vilken implementering av gränssnittet som ska användas inom kontrollenheten.
För nu, låt oss lägga till detta bindande till
app / routes.php
. Senare använder vi istället tjänsteleverantörer för att lagra denna typ av logik.# app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ EloquentPostRepository');Verbalize detta funktionssamtal som, "Laravel, älskling, när du behöver en förekomst av
PostRepositoryInterface
, Jag vill att du ska användaEloquentPostRepository
."
app / förråd / EloquentPostRepository
kommer helt enkelt vara ett omslag runt Eloquent som implementerarPostRepositoryInterface
. På det här sättet begränsar vi inte API: n (och alla andra genomföranden) till Eloquents tolkning. vi kan namnge metoderna men vi önskar.Vissa kan hävda att
Posta
modellen ska injiceras i denna implementering för testbarhetsändamål. Om du håller med, helt enkelt injicera det genom konstruktören, per vanligt.Det är allt det borde ta! Uppdatera webbläsaren, och sakerna bör återgå till det normala. Endast nu är din ansökan mycket bättre strukturerad och regulatorn är inte längre länkad till Eloquent.
Låt oss föreställa oss att, några månader från nu, informerar chefen om att du behöver byta Eloquent ut med Redis. Tja, för att du har strukturerat din ansökan på det här framtidssäkra sättet behöver du bara skapa det nya
app / förråd / RedisPostRepository
genomförande:Och uppdatera bindningen:
# app / routes.php App :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ RedisPostRepository');Omedelbart använder du nu Redis i din controller. Lägg märke till hur
app / controllers / PostsController.php
blev aldrig rörd? Det är skönheten i den!
Strukturera
Hittills i denna lektion har vår organisation varit lite bristfällig. IoC bindningar i
routes.php
fil? Alla repositorier grupperade ihop i en katalog? Visst, det kan fungera i början, men mycket snabbt kommer det att bli uppenbart att det här inte skala.I den sista delen av den här artikeln ska vi PSR-ify vår kod och utnyttja tjänsteleverantörer för att registrera eventuella bindningar.
PSR-0 definierar de obligatoriska kraven som måste följas för autoloaderinteroperabilitet.
En PSR-0-laddare kan registreras hos Composer, via
fso-0
objekt.// composer.json "autoload": "psr-0": "Way": "app / lib /"Syntaxen kan vara förvirrande först. Det var verkligen för mig. Ett enkelt sätt att dechiffrera
"Sätt": "app / lib /"
är att tänka på dig själv "Basmappen förSätt
namnrymden finns iapp / lib
."Naturligtvis, ersätt mitt efternamn med namnet på ditt projekt. Den katalogstruktur som matchar detta skulle vara:
Nästa, istället för att gruppera alla repositories in a förråd
katalog, en mer elegant metod kan vara att kategorisera dem i flera kataloger, som så:
Det är viktigt att vi följer den här namngivnings- och mappkonventionen, om vi vill att autoloading ska fungera som förväntat. Det enda kvar att göra är att uppdatera namnrymdena för PostRepositoryInterface
och EloquentPostRepository
.
Och för genomförandet:
Där går vi det är mycket renare. Men hur är det med de irriterande bindningarna? Ruttfilen kan vara en lämplig plats att experimentera, men det är ingen mening att lagra dem permanent. I stället använder vi tjänsteleverantörer.
Tjänsteleverantörer är inget annat än bootstrap-klasser som kan användas för att göra allt du vill: registrera en bindning, haka i en händelse, importera en ruttfil, etc..
En tjänsteleverantörs
Registrera()
kommer att utlösas automatiskt av Laravel.app-> bind ('Way \ Storage \ Post \ PostRepositoryInterface', 'Way \ Storage \ Post \ EloquentPostRepository');För att göra den här filen känd för Laravel behöver du bara inkludera den i
app / config / app.php
, inomleverantörer
array.# app / config / app.php 'providers' => array ('Illuminate \ Foundation \ Providers \ ArtisanServiceProvider', 'Illuminate \ Auth \ AuthServiceProvider', // ... 'Way \ Storage \ StorageServiceProvider')Bra; nu har vi en dedikerad fil för registrering av nya bindningar.
Uppdatering av testen
Med vår nya struktur på plats, i stället för att spotta Eloquent-modellen, kan vi i stället spotta
PostRepositoryInterface
. Här är ett exempel på ett sådant test:# app / test / controllers / PostsControllerTest.php public function testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ Mock-> shouldReceive ( 'alla') -> en gång (); $ this-> app-> instance ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg');Vi kan dock förbättra detta. Det står till grund att varje metod inom
PostsControllerTest
kommer att kräva en mocked version av förvaret. Som sådan är det bättre att extrahera några av detta prep-arbete till sin egen metod, som så:# app / test / controllers / PostsControllerTest.php public function setUp () förälder :: setUp (); $ This-> mock (Way \ Förvaring \ Post \ PostRepositoryInterface '); public function mock ($ class) $ mock = Mockery :: mock ($ class); $ this-> app-> instance ($ class, $ mock); returnera $ mock; public function testIndex () $ this-> mock-> shouldReceive ('all') -> en gång (); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg');Inte dåligt, hej?
Nu, om du vill vara super-flyga och är villig att lägga till en touch av testlogik till din produktionskod, kan du till och med utföra din mocking inom Eloquent-modellen! Detta skulle möjliggöra
Post :: shouldReceive ( 'alla') -> en gång ();Bakom kulisserna skulle det här spotta
PostRepositoryInterface
, och uppdatera IoC-bindningen. Du kan inte bli mycket mer läsbar än det!Att tillåta denna syntax kräver bara att du uppdaterar
Posta
modell eller, bättre, aBaseModel
att alla Eloquent-modellerna sträcker sig. Här är ett exempel på den tidigare:Om du kan hantera det inre "Ska jag integrera testlogiken i produktionskoden"strid, hittar du att detta möjliggör avsevärt mer läsliga tester.
en gång(); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'inlägg'); public function testStoreFails () Input :: ersätt ($ input = ['title' => "]); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.create '); $ this-> assertSessionHasErrors (); public function testStoreSuccess () Input :: ersätt ($ input = [' title '=>' Foo Title ']); Post :: shouldReceive (' create ') -> en gång (); $ this-> call ('POST', 'posts'); $ this-> assertRedirectedToRoute ('posts.index', ['flash']);Det känns bra, eller hur? Förhoppningsvis har den här artikeln inte varit för överväldigande. Nyckeln är att lära sig hur man organiserar dina förråd på ett sådant sätt att de blir så lätta som möjligt att spotta och injicera i dina kontroller. Som ett resultat av den ansträngningen kommer dina test bli blixtsnabba!
Denna artikel är ett utdrag ur min kommande bok, Laravel Testing Decoded. Håll dig klar för sin release i maj 2013!