Testarea controlorilor nu este cel mai simplu lucru din lume. Ei bine, permiteți-mi să reformulez acest lucru: testarea lor este un cinch; ceea ce este dificil, cel puțin la început, este determinant ce a testa.
În cazul în care un test de control verifică textul pe pagină? Ar trebui să atingă baza de date? Ar trebui să se asigure că variabilele există în vedere? Dacă aceasta este prima dvs. fân-plimbare, aceste lucruri pot fi confuz! Lasă-mă să te ajut.
Testele controlerului ar trebui să verifice răspunsurile, să se asigure că sunt declanșate metodele corecte de acces la baza de date și să afirme că variabilele de instanță corespunzătoare sunt trimise la vizualizare.
Procesul de testare a unui controler poate fi împărțit în trei bucăți.
Vedere
).Cel mai bun mod de a învăța aceste lucruri este prin exemple. Iată "Salut Lume"de testare a controlorului în Laravel.
client-> cerere ("GET", "posturi");
Laravel utilizează o mână de componente Symfony pentru a ușura procesul de testare a rutelor și vederilor, inclusiv HttpKernel, DomCrawler și BrowserKit. Acesta este motivul pentru care este foarte important ca testele dvs. PHPUnit să moștenească, nu PHPUnit \ _Framework \ _TestCase
, dar testcase
. Nu vă faceți griji, Laravel îi extinde încă pe primul, dar ajută la configurarea aplicației Laravel pentru testare, precum și oferă o varietate de metode de afirmare a ajutorului pe care sunteți încurajat să le utilizați. Mai multe despre asta în curând.
În fragmentul de cod de mai sus, facem a OBȚINE
cererea de a / posturi
, sau localhost: 8000 / posturi
. Presupunând că această linie este adăugată unei instalații proaspete de Laravel, Symfony va arunca o NotFoundHttpException
. Dacă lucrați de-a lungul timpului, încercați să îl executați PHPUnit
din linia de comandă.
$ phpunit 1) PostsControllerTest :: testIndex Simfonia \ Component \ HttpKernel \ Exception \ NotFoundHttpException:
În uman-vorbi, acest lucru se traduce, în esență, la "Hei, am încercat să chem această rută, dar nu ai nimic înregistrat, prostule!"
După cum vă puteți imagina, acest tip de solicitare este destul de comună până la punctul în care este logic să se ofere o metodă de ajutor, cum ar fi $ This-> apel ()
. De fapt, Laravel face același lucru! Aceasta înseamnă că exemplul anterior poate fi refactat, după cum urmează:
# app / teste / controlori / PostsControllerTest.php funcția publică testIndex () $ this-> call ('GET', 'posts');
Deși ne vom baza pe funcționalitatea de bază din acest capitol, în proiectele mele personale, iau lucrurile un pas înainte, permițând astfel de metode $ This-> get ()
, $ This-> post ()
, etc Datorită suprasolicitării PHP, acest lucru necesită doar adăugarea unei singure metode, la care ați putea adăuga app / teste / TestCase.php
.
# app / teste / TestCase.php funcția publică __call ($ method, $ args) if (in_array ($ method, ['get' returnează $ this-> call (metoda $, $ args [0]); arunca noua BadMethodCallException;
Acum ești liber să scrii $ This-> get ( 'mesajele')
și să obțină exact același rezultat ca și cele două exemple anterioare. După cum sa menționat mai sus, totuși, hai să rămânem cu funcționalitatea de bază a cadrului pentru simplitate.
Pentru a face testul, trebuie să pregătim calea potrivită.
Alergare
PHPUnit
din nou ne va reveni la verde.
Afirmațiile ajutatorului lui Laravel
Un test pe care îl veți găsi scris în mod repetat este acela care asigură că un controler transmite o anumită variabilă unei vizualizări. De exemplu,
index
Metodă dePostsController
ar trebui să treacă aposturi $
variabilă în vederea asociată, nu? În acest fel, vizualizarea poate să filtreze prin toate postările și să le afișeze pe pagină. Acesta este un test important de scris!Dacă este o sarcină obișnuită, atunci, din nou, nu ar avea sens ca Laravel să ofere o afirmație ajutoare pentru a realiza acest lucru? Desigur, ar fi. Și, bineînțeles, Laravel face!
Illuminate \ Fundația \ Testarea \ testcase
include un număr de metode care vor reduce drastic cantitatea de cod necesară pentru a face afirmații de bază. Această listă include:
assertViewHas
assertResponseOk
assertRedirectedTo
assertRedirectedToRoute
assertRedirectedToAction
assertSessionHas
assertSessionHasErrors
Următoarele exemple solicită GET / postări
și verifică faptul că opiniile sale primesc variabila, posturi $
.
# app / teste / controlori / PostsControllerTest.php funcția publică testIndex () $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posturi');
Bacsis: Când vine vorba de formatare, prefer să ofere o pauză de linie între aserțiunea unui test și codul care pregătește etapa.
assertViewHas
este pur și simplu un pic de zahăr care inspectează obiectul de răspuns - care este returnat de la $ This-> apel ()
- și verifică dacă datele asociate vizualizării conțin a posturi
variabil.
Când inspectați obiectul de răspuns, aveți două opțiuni de bază.
$ Response-> getOriginalContent ()
: Preluați conținutul original sau cel returnat Vedere
. Opțional, puteți accesa original
proprietate direct, mai degrabă decât a apela getOriginalContent
metodă.$ Response-> getContent ()
: Preluați ieșirea randată. În cazul în care un Vedere
instanta este returnata de pe traseu, atunci getContent ()
va fi egal cu ieșirea HTML. Acest lucru poate fi util pentru verificările DOM, cum ar fi "vizualizarea trebuie să conțină acest șir."Să presupunem că posturi
traseul constă din:
Ar trebui să fugim
PHPUnit
, se va stoarce cu un ajutor urmatorul pas mesaj:1) PostsControllerTest :: testIndex Nu a reușit să se afirme că o matrice are cheia 'posts'.Pentru ao face verde, pur și simplu preluăm posturile și le transmitem la vedere.
# app / routes.php Traseu :: get ('posts', function () $ posts = Post :: all (); return Vezi :: make ('posts.index', ['posts', $ posts)) ;);Un lucru de retinut este ca, asa cum codul se afla in prezent, se asigura doar ca variabila,
posturi $
, este trecut la vedere. Nu-i inspectă valoarea.assertViewHas
acceptă opțional un al doilea argument pentru a verifica valoarea variabilei, precum și existența acesteia.# app / teste / controlori / PostsControllerTest.php funcția publică testIndex () $ this-> call ('GET', 'posts'); $ this-> assertViewHas ("posturi", "foo");Cu acest cod modificat, vizualizarea are o variabilă,
posturi $
, care este egal cufoo
, testul va eșua. În această situație, totuși, este posibil să nu specificăm o valoare, ci să declarăm că valoarea este o instanță a lui LaravelIlluminate \ Baza de date \ Elocvent \ Colectia
clasă. Cum putem realiza asta? PHPUnit oferă un ajutorassertInstanceOf
afirmare pentru a satisface această nevoie!# app / teste / controlori / PostsControllerTest.php funcția publică testIndex () $ response = $ this-> apel ('GET', 'posts'); $ This-> assertViewHas ( 'posturi'); // getData () returnează toate vars-urile atașate la răspuns. $ posts = $ răspuns-> original-> getData () ['posturi']; $ this-> assertInstanceOf ('Iluminare \ Bază de date \ Eloquent \ Collection', $ posts);Cu această modificare, am declarat că operatorul trebuie sa trece
posturi $
- un exemplu deIlluminate \ Baza de date \ Elocvent \ Colectia
- la vedere. Excelent.
Mocking baza de date
Există o problemă flagrantă cu testele noastre până acum. L-ai prins?
Pentru fiecare test, o interogare SQL este executată în baza de date. Deși acest lucru este util pentru anumite tipuri de testare (acceptare, integrare), pentru testarea controlerului de bază, acesta va servi doar la scăderea performanței.
Am forat acest lucru în craniu de mai multe ori în acest moment. Nu ne interesează să testați capacitatea lui Eloquent de a prelua înregistrări dintr-o bază de date. Are propriile teste. Taylor știe că funcționează! Să nu pierdem timpul și puterea de procesare repetând aceleași teste.
În schimb, este mai bine să bateți baza de date și pur și simplu să verificați dacă metodele potrivite sunt apelate cu argumentele corecte. Sau, cu alte cuvinte, vrem să ne asigurăm acest lucru
Post :: toate ()
niciodată nu incendiază și nu lovește baza de date. Știm că funcționează, deci nu necesită testare.Această secțiune va depinde foarte mult de biblioteca Mockery. Consultați capitolul din cartea mea, dacă nu sunteți încă familiarizat cu aceasta.
Refactorizarea necesară
Din păcate, până acum, am structurat codul într-un mod care face practic imposibilă testarea.
# app / routes.php Route :: get ('posts', function () // Ouch.Nu putem testa acest lucru $ posts = Post :: all (); index ') -> cu (' posts ', $ posts););Tocmai de aceea este considerată o practică nepotrivită de a încorpora apelurile elocvente în controlorii dvs. Nu confunda fatadele lui Laravel, care sunt testabile și pot fi schimbate cu falsuri (
Coadă :: shouldReceive ()
), cu modelele tale Elocvente. Soluția este de a injecta stratul bazei de date în controler prin constructor. Acest lucru necesită unele refactorizări.Avertizare: Depozitarea logicii în apelurile de rută este utilă pentru proiectele mici și pentru API-uri, dar acestea fac testări incredibil de dificile. Pentru aplicații de orice dimensiune considerabilă, utilizați controlori.
Să înregistrăm o nouă resursă înlocuind
posturi
ruta cu:# app / routes.php Route :: resursă ('posturi', 'PostsController');... și să creeze controllerul necesar cu Artisan.
$ php controller artizan: face PostsController Controller creat cu succes!Acum, mai degrabă decât să se refere la
Post
model direct, îl vom injecta în constructorul controlerului. Iată un exemplu condensat care omite toate metodele odihnitoare, cu excepția celei pe care suntem interesați în prezent să o testăm.post = $ post; indexul funcției publice () $ posts = $ this-> post-> all (); retur View :: make ('posts.index') -> cu ('posts', $ posts);Rețineți că este o idee mai bună să tastați o interfață, mai degrabă decât să faceți referire la modelul elocvent. Dar, un singur lucru la un moment dat! Să lucrăm la asta.
Aceasta este o modalitate semnificativ mai bună de a structura codul. Deoarece modelul este acum injectat, avem posibilitatea de a schimba acest lucru cu o versiune falsă pentru testare. Iată un exemplu de a face exact acest lucru:
mock = batjocură :: mock ("Elocvent", "Post"); funcția publică tearDown () Mockery :: close (); funcția publică testIndex () $ this-> mock -> shouldReceive ('toate') -> o dată () -> șiReturn ('foo'); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posturi');Principalul avantaj al acestei restructurări este că, acum, baza de date nu va fi lovită niciodată în mod inutil. În schimb, folosindu - ne de Mockery, pur și simplu verificăm dacă
toate
metoda este declanșată pe model.$ this-> mock -> shouldReceive ('toate') -> o dată ();Din păcate, dacă alegeți să renunțați la codificarea la o interfață, și în loc să injectați
Post
model în controler, un pic de trickery trebuie să fie folosite pentru a obține în jurul valorii de Eloquent utilizarea de statică, care se poate ciocni cu Mockery. Acesta este motivul pentru care am deturnat ambelePost
șiElocvent
clase în cadrul constructorului testului, înainte ca versiunile oficiale să fie încărcate. În acest fel, avem o ardezie curată pentru a declara orice așteptări. Dezavantajul, desigur, este că nu putem să ne implicăm în nici o metodă existentă, prin folosirea unor metode de batjocurămakePartial ()
.Containerul IoC
Containerul IoC de la Laravel ușurează drastic procesul de injectare a dependențelor în clasele tale. De fiecare dată când este cerut un controler, acesta este rezolvat din containerul IoC. Ca atare, când trebuie să declarăm că o versiune machetă
Post
ar trebui să fie utilizate pentru testare, avem nevoie doar de a oferi Laravel cu instanța dePost
care ar trebui să fie utilizate.$ this-> app-> instance ('Post', $ this-> mock);Gândiți-vă la acest cod, spunând:Hey Laravel, când ai nevoie de o instanță
Post
, Vreau să-mi folosești versiunea martoră."Deoarece aplicația extinderecipient
, avem acces direct la toate metodele IoC.La instanțierea controlorului, Laravel folosește puterea reflecției PHP pentru a citi tipul de tip și a injecta dependența pentru tine. Asta e corect; nu trebuie să scrieți o singură obligație pentru a permite acest lucru; este automatizată!
redirecționări
O altă așteptare obișnuită pe care o veți găsi în scris este aceea care asigură că utilizatorul este redirecționat către locația potrivită, poate după adăugarea unui post nou în baza de date. Cum putem realiza asta??
# app / teste / controlori / PostsControllerTest.php funcția publică testStore () $ this-> mock -> shouldReceive ('crea') -> o dată (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute ( 'posts.index');Presupunând că urmăm o aromă odihnitoare, pentru a adăuga o nouă postare, am face-o
POST
la colecția sauposturi
(nu confundațiPOST
metoda de solicitare cu numele resursei, care se întâmplă să aibă același nume).$ this-> call ('POST', 'posts');Apoi, trebuie doar să folosim alte afirmații ale ajutoarelor lui Laravel,
assertRedirectedToRoute
.Bacsis: Când o resursă este înregistrată la Laravel (
Route :: resurse ()
), cadrul va înregistra automat căile necesare. Alergaphp artizan rute
dacă ați uitat vreodată ce sunt aceste nume.S-ar putea să preferați să vă asigurați, de asemenea, că
$ _POST
superglobal este trecut lacrea
metodă. Chiar dacă nu prezentăm fizic o formă, putem totuși să permitem acest lucru, prin intermediulIntrare :: înlocuiți ()
metodă, care ne permite să "stub" această matrice. Iată testul modificat, care utilizează Mockery'scu()
pentru a verifica argumentele transmise metodei la care se face referireshouldReceive
.# app / teste / controlori / PostsControllerTest.php funcția publică testStore () Input :: replace ($ input = ['title' => 'Titlul meu']); $ this-> mock -> shouldReceive ('crea') -> o dată () -> cu ($ input); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); $ This-> assertRedirectedToRoute ( 'posts.index');
Căi
Un lucru pe care nu l-am luat în considerare în acest test este validarea. Ar trebui să existe două căi separate prin
magazin
, în funcție de validarea:
- Redirecționați înapoi la formularul "Creați postare" și afișați erorile de validare a formularului.
- Redirecționați către colecție sau pe ruta denumită,
posts.index
.Ca o bună practică, fiecare test ar trebui să reprezinte doar o cale prin codul dvs..
Această primă cale va fi pentru validarea nereușită.
# app / teste / controlori / PostsControllerTest.php funcția publică testStoreFails () // Setați etapa pentru o validare nereușită Input :: replace (['title' => "]); $ this-> app-> instance '$ this-> call (' POST ',' posts '); // Validarea eșuată ar trebui să reîncărcați formularul $ create-> assertRedirectedToRoute (' posts.create '); // Erorile ar trebui trimise la vizualizarea $ this-> assertSessionHasErrors (['title']);Fragmentul de cod de mai sus explică în mod explicit ce erori ar trebui să existe. Alternativ, puteți să omiteți argumentul
assertSessionHasErrors
, caz în care va verifica pur și simplu faptul că un sac de mesaje a fost fulgerat (în traducere, include Redirecționarea dvs.)withErrors (erori $)
).Acum, pentru testul care se ocupă de validarea cu succes.
# app / teste / controlori / PostsControllerTest.php funcția publică testStoreSuccess () // Stabiliți etapa validării cu succes Input :: replace (['title' => 'Foo Title']); $ this-> mock -> shouldReceive ('crea') -> o dată (); $ this-> app-> instance ('Post', $ this-> mock); $ this-> call ('POST', 'posts'); // ar trebui să redirecționeze la colecție, cu un mesaj flash de succes $ this-> assertRedirectedToRoute ('posts.index', ['flash']);Codul de producție pentru aceste două teste ar putea să arate astfel:
# app / controllers / PostsController.php funcția publică de stocare () $ input = Input :: all (); // Vom executa validarea în controler pentru comoditate // Ar trebui să exportați acest lucru în model sau un serviciu $ v = Validator :: make ($ input, ['title' => 'required']); dacă ($ v-> eșuează ()) returnează Redirect :: route ('posts.create') -> cuInput () -> cuErrors ($ v-> messages ()); $ this-> post-> create ($ input); returneaza Redirect :: route ('posts.index') -> cu ('flash', 'Mesajul tau a fost creat!');Observați cum
validator
este imbricat direct în controler? În general, aș recomanda să rezumați acest lucru la un serviciu. În acest fel, puteți să vă testați validarea în mod izolat de orice controlere sau rute. Cu toate acestea, să lăsăm lucrurile așa cum sunt pentru simplitate. Un lucru pe care trebuie să-l țineți cont este că nu ne batemvalidator
, deși cu siguranță ați putea face acest lucru. Deoarece această clasă este o fațadă, ea poate fi schimbată cu ușurință cu o versiune batjocorită, prin FatadeshouldReceive
fără a fi nevoie să ne îngrijim de injectarea unui exemplu prin constructor. Victorie!# app / controllers / PostsController.php Validatorul :: shouldReceive ('make') -> o dată () -> șiReturn (Mockery :: mock (['fails' => 'true']));Din când în când, veți descoperi că o metodă care trebuie să fie batjocorită ar trebui să returneze un obiect în sine. Din fericire, cu Mockery, aceasta este o bucată de tort: avem nevoie doar de a crea o momeală anonimă și de a trece o matrice, care semnalează numele metodei și valoarea răspunsului, respectiv. Ca atare:
Mockery :: mock (['fails' => 'true'])va pregati un obiect care sa contina a
eșuează ()
care se întoarceAdevărat
.
Repozitorii
Pentru a permite o flexibilitate optimă, mai degrabă decât să creați o legătură directă între controler și ORM, cum ar fi Eloquent, este mai bine să codificați o interfață. Avantajul considerabil pentru această abordare este că, dacă ar fi posibil să trebuiască să schimbați elocventul, de exemplu, Mongo sau Redis, acest lucru cere literalmente modificarea unei singure linii. Chiar mai bine, controlerul nu trebuie niciodată să fie atins.
Depozitele reprezintă stratul de acces la date al aplicației dvs..
Ce ar putea fi o interfață pentru gestionarea stratului de bază de date a
Post
arată ca? Ar trebui să începi.Acest lucru poate fi cu siguranță extins, dar am adăugat metode minime goale pentru demo:
toate
,găsi
, șicrea
. Rețineți că interfețele depozitului sunt stocate în interiorulapp / inventare
. Deoarece acest folder nu este autoloadat în mod implicit, trebuie să actualizămcomposer.json
dosar pentru ca cererea să o menționeze.// composer.json "autoload": "classmap": [// ... "app / repositories"]Atunci când se adaugă o nouă clasă în acest director, nu uitați să
compozitor dump-autoload -o
.-o
, (optimiza) este opțională, dar ar trebui să fie întotdeauna utilizată ca o bună practică.Dacă încercați să injectați această interfață în controlerul dvs., Laravel vă va atașa. Dați-i drumul; încercați și vedeți. Iată modificat
PostController
, care a fost actualizat pentru a injecta o interfață, mai degrabă decâtPost
Modelul elocvent.post = $ post; indexul funcției publice () $ posts = $ this-> post-> all (); retur View :: make ('posts.index', ['posts' => $ posts));Dacă rulați serverul și vizualizați ieșirea, veți fi întâmpinați cu pagina de eroare grozavă (dar frumoasă) Whoops, declarând că "PostRepositoryInterface nu este instanțiabilă."
Dacă te gândești la asta, bineînțeles că acest cadru este înfricoșător! Laravel este inteligent, dar nu este un cititor de minte. Trebuie să se spună ce implementare a interfeței trebuie utilizată în cadrul controlerului.
Pentru moment, să adăugăm această obligație
app / routes.php
. Mai târziu, vom folosi furnizorii de servicii pentru a stoca acest tip de logică.# app / routes.php App :: bind ("Repositories \ PostRepositoryInterface", "Repositories \ EloquentPostRepository");Verbalizați această funcție apelați ca "Laravel, iubito, când ai nevoie de o instanță
PostRepositoryInterface
, Vreau să o foloseștiEloquentPostRepository
."
app / arhive / EloquentPostRepository
va fi pur și simplu un înveliș în jurul valorii de Elocvent care implementeazăPostRepositoryInterface
. În acest fel, nu restricționăm API (și nici o altă implementare) în interpretarea lui Eloquent; putem numi metodele pe care le dorim.Unii ar putea susține că
Post
modelul ar trebui să fie injectat în această implementare pentru scopuri de testare. Dacă sunteți de acord, pur și simplu o injectați prin constructor, de obicei.Asta ar trebui să fie! Reîmprospătați browserul și lucrurile ar trebui să revină la normal. Doar acum, aplicația dvs. este mult mai bine structurată, iar controlorul nu mai este legat de elocvent.
Să ne imaginăm că, la câteva luni de acum, șeful dvs. vă informează că trebuie să schimbați elocventul cu Redis. Ei bine, pentru că ți-ai structurat aplicația în acest mod sigur, nu trebuie decât să creezi noul
app / arhive / RedisPostRepository
implementare:Și actualizați legarea:
# app / routes.php Aplicație :: bind ('Repositories \ PostRepositoryInterface', 'Repositories \ RedisPostRepository');În mod instantaneu, acum folosiți Redis în controlorul dvs. Observați cum
app / controlere / PostsController.php
nu a fost niciodată atins? Aceasta este frumusețea ei!
Structura
Până acum, în această lecție, organizația noastră a lipsit puțin. IoC legături în
routes.php
fişier? Toate depozitele grupate într-un singur director? Sigur, asta ar putea funcționa la început, dar, foarte repede, va deveni evident că acest lucru nu scade.În secțiunea finală a acestui articol, vom respecta codul nostru PSR și le vom furniza furnizorilor de servicii să înregistreze toate legările aplicabile.
PSR-0 definește cerințele obligatorii care trebuie respectate pentru interoperabilitatea autoloader.
Un încărcător PSR-0 poate fi înregistrat la Compozitor, prin intermediul
psr-0
obiect.// compozitor.json "autoload": "psr-0": "Way": "app / lib /"Sintaxa poate fi confuză la început. Cu siguranță a fost pentru mine. O modalitate ușoară de a descifra
"Way": "app / lib /"
este să te gândești la tine însuți "Dosarul de bază pentruCale
spațiul de nume este localizat înapp / lib
."Desigur, înlocuiți numele meu de familie cu numele proiectului dvs. Structura directorului pentru a se potrivi cu aceasta ar fi:
Apoi, mai degrabă decât gruparea tuturor depozitelor într-un arhive
director, o abordare mai elegantă ar putea fi clasificarea acestora în mai multe directoare, cum ar fi:
Este vital să aderăm la această convenție de numire și dosar, dacă vrem ca autoloading să funcționeze așa cum era de așteptat. Singurul lucru rămas de făcut este actualizarea spațiilor de nume pentru PostRepositoryInterface
și EloquentPostRepository
.
Și pentru punerea în aplicare:
Acolo mergem; este mult mai curat. Dar cum rămâne cu legăturile astea? Fișierul de rute poate fi un loc convenabil pentru a experimenta, dar nu are sens să-i păstrați permanent acolo. În schimb, vom utiliza furnizorii de servicii.
Furnizorii de servicii nu sunt decât clase de bootstrap care pot fi folosite pentru a face orice doriți: înregistrați o legare, cârlig într-un eveniment, importați un fișier de rute etc..
Un furnizor de servicii
Inregistreaza-te()
va fi declanșată automat de către Laravel.app-> bind ("Mod \ Storage \ Post \ PostRepositoryInterface", "Way \ Storage \ Post \ EloquentPostRepository");Pentru a face cunoscut acest fișier Laravel, trebuie doar să îl includeți
app / config / app.php
, în cadrulfurnizorii de
mulțime.# furnizori / config / app.php "furnizori" => array ('Illuminate \ Foundation \ Providers \ ArtisanServiceProvider', 'Illuminate \ Auth \ AuthServiceProvider'Bun; acum avem un fișier dedicat pentru înregistrarea noilor legări.
Actualizarea testelor
Cu structura noastră nouă în loc, mai degrabă decât batjocorind modelul Elocvent, în schimb, putem să frământăm
PostRepositoryInterface
. Iată un exemplu al unui astfel de test:# app / teste / controlori / PostsControllerTest.php funcția publică testIndex () $ mock = Mockery :: mock ('Way \ Storage \ Post \ PostRepositoryInterface'); $ Mock-> shouldReceive ( 'toate') -> o dată (); $ this-> app-> instanță ('Way \ Storage \ Post \ PostRepositoryInterface', $ mock); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posturi');Cu toate acestea, putem îmbunătăți acest lucru. Se justifică faptul că fiecare metodă din cadrul acesteia
PostsControllerTest
va necesita o versiune martoră a depozitului. Ca atare, este mai bine să extrageți o parte din această pregătire în metoda proprie, cum ar fi:# app / teste / controlori / PostsControllerTest.php funcția publică setUp () parent :: setUp (); $ This-> bate joc ( 'Way \ Storage \ Mesaj \ PostRepositoryInterface'); funcția publică funcțională ($ class) $ mock = Mockery :: mock ($ class); $ this-> app-> instanță ($ class, $ mock); întoarce $ mock; funcția publică testIndex () $ this-> mock-> shouldReceive ('toate') -> o dată (); $ this-> call ('GET', 'posts'); $ This-> assertViewHas ( 'posturi');Nu-i rău, ay?
Acum, dacă doriți să fiți super-zburați și sunteți dispuși să adăugați o notă de logică de testare la codul dvs. de producție, ați putea chiar să vă faceți o batjocură în cadrul modelului Elocvent! Acest lucru ar permite:
Post :: shouldReceive ( 'toate') -> o dată ();În spatele scenei, acest lucru ar fi înșelat
PostRepositoryInterface
, și să actualizeze legarea IoC. Nu poți fi mult mai ușor de citit decât asta!Permiterea acestei sintaxă necesită doar actualizarea
Post
model, sau, mai bine, aBaseModel
că toate modelele elocvente se extind. Iată un exemplu al primului:Dacă puteți gestiona interiorul "Ar trebui să încorporez logica testului în codul de producție"bătălie, veți găsi că acest lucru permite teste mult mai ușor de citit.