Laravel API: Kako kreirati i testirati RESTful API
Laravel framework štedi vrijeme programerima dajući prednost konvenciji u odnosu na konfiguraciju.
Laravel ima za cilj da se razvija zajedno s webom i brzo uključuje nekoliko inovativnih karakteristika i ideja iz svijeta web razvoja.
Redovi poslova, Laravel API autentikacija, komunikacija u realnom vremenu i još mnogo toga.
- laravel RESTful api
- HTTP glagoli predstavljaju radnje
- Akcija ažuriranja: PUT vs. POŠTA
- laravel API resurs
- Napomena o konzistentnosti
- Podešavanje projekta za Laravel web servis
- Migracije i modeli
- Seeding baze podataka
- Rute i kontroleri
- HTTP statusni kodovi i format odgovora
- Slanje ispravnog 404 odgovora
- laravel api autentikacija
- Kreiranje krajnje tačke registra
- Kreiranje krajnje tačke za prijavu
- Isključivanje
- Upotreba međuvera za ograničavanje pristupa
- Testirajte naše krajnje tačke
- Postavljanje fabrika za testiranje
- Naši prvi testovi
13 minute
Da li ste specijalizovani za razvoj softvera za pružanje onlajn usluga? Pa, onda stvarno mislim da biste trebali razmisliti o implementaciji RESTful API-ji sa okvirom Laravel.
Na taj način ćete moći proizvoditi softver fokusirajući se na analizu, čisti kod i testirati njegovo funkcioniranje na jednostavan i potpun način.
Dakle, ako želite znati više i želite saznati kako to implementirati RESTful API-ji con Laravel, predlažem da odvojite nekoliko slobodnih minuta samo za sebe i odmah se počnete fokusirati na ovaj moj vodič fokusiran na temu. Jeste li spremni? Da ? Vrlo dobro: hajde da prestanemo da pričamo i idemo dalje!
U ovom vodiču ćemo vidjeti kako ga kreirati i testirati Laravel RESTful API i laravel api autentikacija čvrsti koristeći okvir Laravel sa autentifikacijom.
laravel RESTful api
REST
stoji za Reprezentativni državni transfer i predstavlja arhitektonski stil za mrežnu komunikaciju između aplikacija, koji se zasniva na protokolu bez stanja (obično HTTP
) za interakciju.
HTTP glagoli predstavljaju radnje
Nelle Laravel API RESTful
, koristimo glagole HTTP
kao akcije i endpoint
oni su resursi na kojima se djeluje. Koristićemo glagole HTTP
za njihovo semantičko značenje:
GET
: oporaviti resursePOST
: kreiranje resursaPUT
: Ažurirajte resurseDELETE
: Izbrišite resurse
Akcija ažuriranja: PUT vs. POŠTA
Što se tiče API RESTful
Postoje različita mišljenja o tome da li nadograditi sa POST
, PATCH
ili PUT
, ili je bolje napustiti akciju create
na glagol PUT
. U ovom članku ćemo koristiti PUT
za akciju ažuriranja, budući da prema HTTP RFC
, PUT
znači kreirati/ažurirati resurs na određenoj lokaciji. Još jedan uslov za glagol PUT
è l’idempotenza, che in questo caso significa sostanzialmente che puoi inviare quella richiesta 1, 2 o 1000 volte e il risultato sarà lo stesso: una risorsa aggiornata nel baza podataka.
laravel API resurs
Resursi će biti ciljevi akcija, u našem slučaju Items
e Users
, a oni će imati svoje krajnja tačka:
- /items
- /korisnici
U ovom tutorijalu na laravel API resurs, le risorse avranno una rappresentazione 1:1 nei nostri modelli di dati. È possibile avere risorse rappresentate in più di un modello dati (o non rappresentate affatto nel baza podataka) e modelli completamente non accessibili all’utente. Alla fine, sarai tu a decidere come progettare risorse e modelli in modo che si adattino alla tua applicazione.
Napomena o konzistentnosti
Najveća prednost korištenja skupa konvencija poput REST
da li je to tvoje API
bit će mnogo lakši za korištenje i razvoj. Neki endpoint
oni su prilično jednostavni i, shodno tome, vaši API
bit će mnogo lakši za korištenje i upravljanje nego endpoint
doći GET /get_item?id_item=12
e POST /delete_item?number=40
.
Međutim, u nekim slučajevima će biti teško mapirati na obrazac za kreiranje/preuzimanje/ažuriranje/brisanje. Zapamtite da URL-ovi ne moraju sadržavati glagole i da resursi nisu nužno redovi u tabeli. Još jedna stvar koju treba imati na umu je da ne morate provoditi svaku akciju za svaki resurs.
Podešavanje projekta za Laravel web servis
Kao i kod svih modernih PHP okvira, trebat će nam by Composer da instaliramo i upravljamo našim zavisnostima. Nakon što slijedimo upute za preuzimanje (i dodamo varijablu okruženja na putanju), hajde da instaliramo Laravel koristeći naredbu:
$ composer global require laravel/installer
Nakon završetka instalacije možete kreirati novu aplikaciju na sljedeći način:
$ laravel new myapp
Za gornju komandu, morate imati ~/composer/vendor/bin
u vašem fajlu $PATH
. Ako ne želite da se bavite time, možete kreirati i novi projekat koristeći Composer, na sledeći način:
$ composer create-project --prefer-dist laravel/laravel myapp
con Laravel
instaliran, trebali biste biti u mogućnosti da pokrenete server i provjerite da li sve radi:
$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>
Kad otvoriš
https://localhost:8000
u vašem pretraživaču, trebali biste vidjeti ovu stranicu za primjer.
Migracije i modeli
Prije nego što napišem naše migration
, dobbiamo assicurarci di avere un baza podataka creato per questa app e aggiungere le sue credenziali al nel file .env
nalazi se u korijenu projekta.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=warehoue
DB_USERNAME=warehoue
DB_PASSWORD=topsecret
Počnimo s našim prvim model
e migration
: theitem
, koji bi trebao imati polje za naziv i polje za cijenu, kao i datum kreiranja. Laravel
pruža nekoliko komandi putem Artisan
, koji nam pomaže da generišemo datoteke tako što ih stavljamo u ispravne fascikle. Za kreiranje modela Item
, možemo pokrenuti:
$ php artisan make:model Item -m
Opcija -m
je skraćenica za --migration
i označava oglas Artisan
da kreiramo jedan za naš model. Evo generirane migracije:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('items');
}
}
Hajde da to raskinemo na sekundu:
- Metode
up()
edown()
izvode se odnosno tokommigrate
i bilo kojirollback
; $table->increments('id')
postavlja cjelobrojno polje koje se automatski povećava tako što ga imenujeid
;$table->timestamps()
će postaviti poljacreated_at
eupdated_at
tipatimestamp
. Ne moramo da brinemo o postavljanju podrazumevane vrednosti, jerLaravel
brine se za ažuriranje ovih polja kada je to potrebno.- I na kraju,
Schema::dropIfExists()
da obrišete tabelu, ako postoji.
Uz to rečeno, dodajmo dva reda našoj metodi up()
:
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->double('price');
$table->timestamps();
});
}
Metoda string()
stvara kolonu za nas VARCHAR
ekvivalentna dok je metoda double()
stvara nas a DOUBLE
ekvivalentan. Kada je ovo urađeno, nastavimo sa migracijom:
$ php artisan migrate
Možete koristiti i opciju --step
, koji odvaja svaku migraciju u vlastitu grupu tako da ih možete pojedinačno vratiti ako je potrebno.
Laravel
dolazi sa dvije migracije create_users_table
e create_password_resets_table
. Nećemo koristiti tabelu password_resets
, ali stol users
biće nam od koristi.
Vratimo se sada na naš model i dodajmo ove atribute polju $fillable
tako da ih možete koristiti u svojim predlošcima Item::create
e Item
::update
class Item extends Model
{
protected $fillable = ['name', 'price'];
}
Polja unutar posjeda $fillable
može se dodijeliti na veliko korištenjem metoda
e create()
di Elokventan. Također možete koristiti nekretninu update()
$guarded
dozvoliti sva svojstva osim nekih.
Seeding baze podataka
Il sjetva baze podataka è il processo di riempimento del baza podataka con dati fittizi che possiamo utilizzare per testarlo. Lar
avel
dolazi sa Faker , sjajna biblioteka za generiranje samo ispravnog formata lažnih podataka za nas. Pa hajde da napravimo našu prvu seeder
:
$ php artisan make:seeder ItemsTableSeeder
I seeders
nalaze se u imeniku /database/seeds
. Evo kako to izgleda nakon što ga podesite da kreira neke items
:
class ItemsTableSeeder extends Seeder
{
public function run()
{
// Let's truncate our existing records to start from scratch.
Item::truncate();
$faker = \Faker\Factory::create();
// And now, let's create a few items in our database:
for ($i = 0; $i < 50; $i++) {
Item::create([
'name' => $faker->sentence,
'price' => $book->setPrice($faker->randomNumber(2)),
]);
}
}
}
Zatim pokrećemo naredbu seed
:
$ php artisan db:seed --class=ItemTableSeeder
Ponavljamo proces da kreiramo a seeder Users
:
class UsersTableSeeder extends Seeder
{
public function run()
{
// Let's clear the users table first
User::truncate();
$faker = \Faker\Factory::create();
// Let's make sure everyone has the same password and
// let's hash it before the loop, or else our seeder
// will be too slow.
$password = Hash::make('bloginnovazione');
User::create([
'name' => 'Administrator',
'email' => 'admin@test.com',
'password' => $password,
]);
// And now let's generate a few dozen users for our app:
for ($i = 0; $i < 10; $i++) {
User::create([
'name' => $faker->name,
'email' => $faker->email,
'password' => $password,
]);
}
}
}
Možemo to olakšati dodavanjem vlastitih sejalice a DatabaseSeederclass glavni unutra baza podataka/ seme folder:
class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(ItemsTableSeeder::class);
$this->call(UsersTableSeeder::class);
}
}
Na ovaj način možemo jednostavno izvršiti $php artisan db:seed
i sve klase pozvane u metodi će biti izvršene run().
Rute i kontroleri
Mi ih stvaramo endpoint
osnovno za našu aplikaciju: kreirajte, preuzmite listu, preuzmite jednu listu, ažurirajte i izbrišite. Na fajlu routes/api.php
, možemo jednostavno uraditi ovo:
Use App\Item;
Route::get('items', function() {
// If the Content-Type and Accept headers are set to 'application/json',
// this will return a JSON structure. This will be cleaned up later.
return Item::all();
});
Route::get('items/{id}', function($id) {
return Items::find($id);
});
Route::post('items', function(Request $request) {
return Item::create($request->all);
});
Route::put('items/{id}', function(Request $request, $id) {
$item = Item::findOrFail($id);
$item->update($request->all());
return $item;
});
Route::delete('items/{id}', function($id) {
Item::find($id)->delete();
return 204;
})
Rute unutra api.php
oni će imati prefiks /api/
i middleware
API ograničenje će se automatski primijeniti na ove rute (ako želite ukloniti prefiks, možete urediti klasu RouteServiceProvider
su /app/Providers/RouteServiceProvider.php
).
Sada premjestimo ovaj kod na vlastiti Controller
:
$ php artisan make:controller ItemController
ItemController.php:
use App\Item;
class ItemController extends Controller
{
public function index()
{
return Item::all();
}
public function show($id)
{
return Item::find($id);
}
public function store(Request $request)
{
return Item::create($request->all());
}
public function update(Request $request, $id)
{
$item = Item::findOrFail($id);
$item->update($request->all());
return $item;
}
public function delete(Request $request, $id)
{
$item = Item::findOrFail($id);
$item->delete();
return 204;
}
}
Fajl routes/api.php
:
Route::get('items', 'ItemController@index');
Route::get('items/{id}', 'ItemController@show');
Route::post('items', 'ItemController@store');
Route::put('items/{id}', 'ItemController@update');
Route::delete('items/{id}', 'ItemController@delete');
Možemo ih poboljšati endpoint
koristeći implicitno povezivanje modela putanje. Na ovaj način, Laravel
će ubaciti instancu Item
u našim metodama i automatski će vratiti 404 ako se ne pronađe. Morat ćemo napraviti promjene u fajlu ruta i kontroleru:
Route::get('items', 'ItemController@index');
Route::get('items/{item}', 'ItemController@show');
Route::post('items', 'ItemController@store');
Route::put('items/{item}', 'ItemController@update');
Route::delete('items/{item}', 'ItemController@delete');
class ItemController extends Controller
{
public function index()
{
return Item::all();
}
public function show(Item $item)
{
return $item;
}
public function store(Request $request)
{
$item= Item::create($request->all());
return response()->json($item, 201);
}
public function update(Request $request, Item $item)
{
$item->update($request->all());
return response()->json($item, 200);
}
public function delete(Item $item)
{
$item->delete();
return response()->json(null, 204);
}
}
HTTP statusni kodovi i format odgovora
Dodajmo response()->json()
ai endpoint
. Ovo nam omogućava da eksplicitno vratimo podatke JSON
i poslati kod HTTP
koje klijent može analizirati. Najčešći kodovi koje ćete vratiti bit će:
200
: OK. Standardni kod uspjeha i zadana opcija.201
: Objekt kreiran. Korisno zastore
akcije.204
: Nema sadržaja. Kada je akcija bila uspješna, ali nema sadržaja za vraćanje.206
: Djelomičan sadržaj. Korisno kada trebate vratiti paginiranu listu resursa.400
: Loš zahtjev. Standardna opcija za zahtjeve koji ne uspijevaju provjeriti valjanost.401
: Nije dopusteno. Korisnik mora biti autentificiran.403
: Zabranjeno. Korisnik je autentificiran, ali nema dozvole za izvođenje radnje.404
: Nije pronađeno. Ovo će automatski vratiti Laravel kada resurs ne bude pronađen.500
: Interna greška servera. U idealnom slučaju, nećete ga eksplicitno vratiti, ali ako se nešto neočekivano pokvari, to će vaš korisnik dobiti.503
: Usluga nedostupna. Prilično samo po sebi razumljivo, ali i još jedan kod koji aplikacija neće eksplicitno vratiti.
Slanje ispravnog 404 odgovora
Ako ste pokušali dohvatiti nepostojeći resurs, dobićete izuzetak i dobiti ceo stacktrace, ovako:
Ovo možemo popraviti tako što ćemo modificirati našu klasu obrađivača izuzetaka, koja se nalazi na app/Exceptions/Handler.php
, da vratite odgovor JSON
:
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
Evo primjera povratka:
{
data: "Resource not found"
}
Ako koristite Laravel
da biste poslužili drugim stranicama, morate izmijeniti kod kako bi radio sa zaglavljem Accept
, inače će 404 greške iz redovnih zahtjeva također vratiti a JSON
.
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException &&
$request->wantsJson())
{
return response()->json([
'data' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
U ovom slučaju, API zahtjevima će biti potrebno zaglavlje Accept: application/json
.
laravel api autentikacija
Postoji mnogo načina za implementaciju laravel api autentikacija (jedan od njih je pasoš , odličan način za implementaciju OAuth2), ali u ovom članku ćemo uzeti vrlo pojednostavljen pristup.
Za početak, morat ćemo dodati polje api_token do stola users
:
$ php artisan make:migration --table=users adds_api_token_to_users_table
A zatim implementirajte migraciju:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 60)->unique()->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['api_token']);
});
}
Nakon toga, jednostavno migrirajte pomoću naredbe zanatlija:
$ php artisan migrate
Kreiranje krajnje tačke registra
Za implementaciju a laravel api autentikacija koristićemo simbol RegisterController
(u folderu Auth
) da vratite tačan odgovor nakon registracije. Laravel Dolazi sa autentifikacijom iz kutije, ali još uvijek ga moramo malo podesiti da bismo dobili odgovor koji želimo.
Kontrolor podataka koristi ovu funkciju za RegistersUsers
implementirati logiranje. Evo kako to funkcionira:
public function register(Request $request)
{
// Here the request is validated. The validator method is located
// inside the RegisterController, and makes sure the name, email
// password and password_confirmation fields are required.
$this->validator($request->all())->validate();
// A Registered event is created and will trigger any relevant
// observers, such as sending a confirmation email or any
// code that needs to be run as soon as the user is created.
event(new Registered($user = $this->create($request->all())));
// After the user is created, he's logged in.
$this->guard()->login($user);
// And finally this is the hook that we want. If there is no
// registered() method or it returns null, redirect him to
// some other URL. In our case, we just need to implement
// that method to return the correct response.
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
Samo treba da implementiramo metodu registered()
u našem fajlu RegisterController
. Metoda prima $request
e $user
, i to je ono što želimo. Evo kako bi metoda trebala izgledati unutar kontrolera:
protected function registered(Request $request, $user)
{
$user->generateToken();
return response()->json(['data' => $user->toArray()], 201);
}
I možemo ga povezati s datotekom putanja:
Route::post('register', 'Auth\RegisterController@register');
U prethodnom dijelu koristili smo metodu na modelu User
za generiranje token
. Ovo je korisno tako da imamo samo jedan način da generišemo i token
. Dodajte sljedeću metodu svom modelu korisnika:
class User extends Authenticatable
{
...
public function generateToken()
{
$this->api_token = str_random(60);
$this->save();
return $this->api_token;
}
}
Korisnik je sada registrovan i zahvaljujući validaciji Laravel
i trenutnu autentifikaciju, polja name
, email
, password
e password_confirmation
oni su obavezni i povratne informacije se obrađuju automatski. Provjerite metodu validator()
unutar RegisterController
da vidite kako se pravila sprovode.
Evo šta dobijamo kada stignemo doendpoint
:
$ curl -X POST http://localhost:8000/api/register \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "john.doe@bloginnovazione.it", "password": "bloginnovazione123", "password_confirmation": "bloginnovazione123"}'
{
"data": {
"api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT",
"created_at": "2017-06-20 21:17:15",
"email": "john.doe@bloginnovazione.it",
"id": 51,
"name": "John",
"updated_at": "2017-06-20 21:17:15"
}
}
Kreiranje krajnje tačke za prijavu
Baš kao iendpoint
registraciju, možemo izmijeniti LoginController
(u folderu Auth
) za podršku našoj API autentifikaciji. Metoda login
di AuthenticatesUsers
može se nadjačati da bi se podržalo naše laravel api autentikacija:
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->attemptLogin($request)) {
$user = $this->guard()->user();
$user->generateToken();
return response()->json([
'data' => $user->toArray(),
]);
}
return $this->sendFailedLoginResponse($request);
}
I možemo ga povezati s datotekom putanja:
Route::post('login', 'Auth\LoginController@login');
Sada, pod pretpostavkom da i seeders
su izvršeni, to je ono što dobijamo kada pošaljemo zahtjev POST
na taj put:
$ curl -X POST localhost:8000/api/login \
-H "Accept: application/json" \
-H "Content-type: application/json" \
-d "{\"email\": \"admin@test.com\", \"password\": \"bloginnovazione\" }"
{
"data": {
"id":1,
"name":"Administrator",
"email":"admin@test.com",
"created_at":"2017-04-25 01:05:34",
"updated_at":"2017-04-25 02:50:40",
"api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw"
}
}
Za slanje token
u zahtjevu, to možete učiniti slanjem atributa api_token
Nel payload
ili kako token
nosilac u zaglavljima zahtjeva u obliku Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw
.
Isključivanje
Sa našom trenutnom strategijom, ako je token pogrešan ili nedostaje, korisnik bi trebao dobiti odgovor bez autentifikacije (koji ćemo implementirati u sljedećem odjeljku). Stoga, za a endpoint
jednostavnog isključenja, mi ćemo poslati token
e verrà rimosso dal baza podataka.
routes/api.php
:
Route::post('logout', 'Auth\LoginController@logout');
Auth\LoginController.php
:
public function logout(Request $request)
{
$user = Auth::guard('api')->user();
if ($user) {
$user->api_token = null;
$user->save();
}
return response()->json(['data' => 'User logged out.'], 200);
}
Koristeći ovu strategiju, bilo koji token koji korisnik ima bit će nevažeći i Laravel API će odbiti pristup (koristeći middleware
, kao što je objašnjeno u sljedećem odjeljku). Ovo mora biti usklađeno s front-endom kako bi se izbjeglo da korisnik ostane prijavljen bez pristupa bilo kakvom sadržaju.
Upotreba međuvera za ograničavanje pristupa
Jednom kreiran api_token
, možemo aktivirati/deaktivirati middleware
autentifikacija u datoteci putanja:
Route::middleware('auth:api')
->get('/user', function (Request $request) {
return $request->user();
});
Trenutnom korisniku možemo pristupiti pomoću metode $request->user()
ili preko facade Auth
Auth::guard('api')->user(); // instance of the logged user
Auth::guard('api')->check(); // if a user is authenticated
Auth::guard('api')->id(); // the id of the authenticated user
I dobijamo ovakav rezultat:
To je zato što moramo promijeniti metodu unauthenticated
aktuelno u našem razredu Handler
. Trenutna verzija vraća a JSON
samo ako zahtjev ima zaglavlje Accept: application/json
, pa hajde da ga promijenimo:
protected function unauthenticated($request, AuthenticationException $exception)
{
return response()->json(['error' => 'Unauthenticated'], 401);
}
Kada se ovaj problem riješi, možemo se vratiti na endpoint
članka da ih umetnete u međuopremu auth:api
. To možemo učiniti pomoću grupa puteva:
Route::group(['middleware' => 'auth:api'], function() {
Route::get('items', 'ItemController@index');
Route::get('items/{item}', 'ItemController@show');
Route::post('items', 'ItemController@store');
Route::put('items/{item}', 'ItemController@update');
Route::delete('items/{item}', 'ItemController@delete');
});
Na ovaj način ne moramo postavljati middleware
za svaku od ruta. Trenutno ne štedi mnogo vremena, ali kako projekat raste, pomaže da se staze održavaju čistima.
Testirajte naše krajnje tačke
Laravel
uključuje integraciju sa PHPUnit
spreman za upotrebu sa fajlom phpunit.xml
već konfigurisan. The framework
također nam pruža nekoliko dodatnih pomoći i tvrdnji koje nam znatno olakšavaju život, posebno za testiranje laravel api autentikacija.
Postoje brojni vanjski alati koje možete koristiti da testirate svoje Laravel API; međutim, testirajte iznutra Laravel
je bolja alternativa: možemo imati sve prednosti testiranja strukture i rezultataAPI mantenendo il pieno controllo del baza podataka. Per l’endpoint
sa liste, na primjer, mogli bismo pokrenuti nekoliko tvornica i reći da odgovor sadrži te resurse.
Per iniziare, dovremo modificare alcune impostazioni per utilizzare un baza podataka SQLite
u memoriji. Njegovo korištenje će učiniti naše testove izuzetno brzim, ali kompromis je u tome što neke naredbe migracije (na primjer, ograničenja) neće raditi ispravno u toj konfiguraciji.
Eseguiremo anche le migrazioni prima di ogni test. Questa configurazione ci consentirà di costruire il baza podataka per ogni test e poi di distruggerlo, evitando qualsiasi tipo di dipendenza tra i test.
U našem dosijeu config/database.php
, morat ćemo postaviti polje database
, u konfiguraciji sqlite
sa :memory:
:
...
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
...
]
Pa hajde da omogućimo SQLite
in phpunit.xml
, dodajući varijablu okruženja DB_CONNECTION
:
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="sqlite"/>
</php>
Uz to, sve što je preostalo je da uspostavimo naš razred TestCase
osnova za korišćenje migration
i seeder
, prije svakog testa. Da bismo to učinili, moramo dodati DatabaseMigrations
a zatim dodajte poziv Artisan
na našu metodu setUp()
.
Evo klase nakon izmjena:
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseMigrations;
public function setUp()
{
parent::setUp();
Artisan::call('db:seed');
}
}
Jedna stvar koju treba dodati je test a komande composer.json
:
"scripts": {
"test" : [
"vendor/bin/phpunit"
],
...
},
Test komanda će biti dostupna ovako:
$ composer test
Postavljanje Factories
za testiranje
Le factories
oni će nam omogućiti da brzo kreiramo objekte sa pravim podacima za testiranje. Oni se nalaze u folderu database/factories
. Laravel
ima jednu factory
za razred User
, pa hajde da dodamo jedan za klasu
:Item
$factory->define(App\Item::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence,
'body' => $faker->paragraph,
];
});
Knjižara Faker je već umetnut kako bi nam pomogao da kreiramo ispravan nasumični format podataka za naše modele.
Naši prvi testovi
Možemo koristiti metode assert of Laravel
da lako dođete do krajnje tačke i procenite njen odgovor. Kreirajmo naš prvi test, test za prijavu, koristeći sljedeću naredbu:
$ php artisan make:test Feature/LoginTest
A evo i našeg testa:
class LoginTest extends TestCase
{
public function testRequiresEmailAndLogin()
{
$this->json('POST', 'api/login')
->assertStatus(422)
->assertJson([
'email' => ['The email field is required.'],
'password' => ['The password field is required.'],
]);
}
public function testUserLoginsSuccessfully()
{
$user = factory(User::class)->create([
'email' => 'testlogin@user.com',
'password' => bcrypt('bloginnovazione123'),
]);
$payload = ['email' => 'testlogin@user.com', 'password' => 'bloginnovazione123'];
$this->json('POST', 'api/login', $payload)
->assertStatus(200)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
'created_at',
'updated_at',
'api_token',
],
]);
}
}
Ove metode testiraju nekoliko jednostavnih slučajeva. Metoda json()
dostiže krajnju tačku, a ostale tvrdnje su prilično jasne. Detalj o assertJson()
: Ova metoda pretvara odgovor u niz koji traži argument, tako da je redoslijed bitan. U ovom slučaju možete povezati više poziva.
Sada napravimo test krajnje tačke registra i napišemo par za tu krajnju tačku:
$ php artisan make:test RegisterTest
class RegisterTest extends TestCase
{
public function testsRegistersSuccessfully()
{
$payload = [
'name' => 'John',
'email' => 'john@bloginnovazione.it',
'password' => 'bloginnovazione123',
'password_confirmation' => 'bloginnovazione123',
];
$this->json('post', '/api/register', $payload)
->assertStatus(201)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
'created_at',
'updated_at',
'api_token',
],
]);;
}
public function testsRequiresPasswordEmailAndName()
{
$this->json('post', '/api/register')
->assertStatus(422)
->assertJson([
'name' => ['The name field is required.'],
'email' => ['The email field is required.'],
'password' => ['The password field is required.'],
]);
}
public function testsRequirePasswordConfirmation()
{
$payload = [
'name' => 'John',
'email' => 'john@bloginnovazione.it',
'password' => 'bloginnovazione123',
];
$this->json('post', '/api/register', $payload)
->assertStatus(422)
->assertJson([
'password' => ['The password confirmation does not match.'],
]);
}
}
I konačno, krajnja tačka odjave:
$ php artisan make:test LogoutTest
class LogoutTest extends TestCase
{
public function testUserIsLoggedOutProperly()
{
$user = factory(User::class)->create(['email' => 'user@test.com']);
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$this->json('get', '/api/items', [], $headers)->assertStatus(200);
$this->json('post', '/api/logout', [], $headers)->assertStatus(200);
$user = User::find($user->id);
$this->assertEquals(null, $user->api_token);
}
public function testUserWithNullToken()
{
// Simulating login
$user = factory(User::class)->create(['email' => 'user@test.com']);
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
// Simulating logout
$user->api_token = null;
$user->save();
$this->json('get', '/api/items', [], $headers)->assertStatus(401);
}
}
Važno je napomenuti da se tokom testiranja Laravel aplikacija ne instancira ponovo na novi zahtjev. To znači da kada dođemo do međuvera za autentifikaciju, on sprema trenutnog korisnika unutar instance TokenGuard
per evitare di colpire nuovamente il baza podataka. Una scelta saggia, tuttavia: in questo caso significa che dobbiamo dividere il test di logout in due, per evitare eventuali problemi con l’utente precedentemente memorizzato nella cache.
Testiranje krajnjih tačaka stavke je također jednostavno:
class ItemTest extends TestCase
{
public function testsItemsAreCreatedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$payload = [
'title' => 'Lorem',
'body' => 'Ipsum',
];
$this->json('POST', '/api/items', $payload, $headers)
->assertStatus(200)
->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']);
}
public function testsItemsAreUpdatedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$item = factory(Item::class)->create([
'title' => 'First Item',
'body' => 'First Body',
]);
$payload = [
'title' => 'Lorem',
'body' => 'Ipsum',
];
$response = $this->json('PUT', '/api/items/' . $item->id, $payload, $headers)
->assertStatus(200)
->assertJson([
'id' => 1,
'title' => 'Lorem',
'body' => 'Ipsum'
]);
}
public function testsArtilcesAreDeletedCorrectly()
{
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$item = factory(Item::class)->create([
'title' => 'First Item',
'body' => 'First Body',
]);
$this->json('DELETE', '/api/items/' . $item->id, [], $headers)
->assertStatus(204);
}
public function testItemsAreListedCorrectly()
{
factory(Item::class)->create([
'title' => 'First Item',
'body' => 'First Body'
]);
factory(Item::class)->create([
'title' => 'Second Item',
'body' => 'Second Body'
]);
$user = factory(User::class)->create();
$token = $user->generateToken();
$headers = ['Authorization' => "Bearer $token"];
$response = $this->json('GET', '/api/items', [], $headers)
->assertStatus(200)
->assertJson([
[ 'title' => 'First Item', 'body' => 'First Body' ],
[ 'title' => 'Second Item', 'body' => 'Second Body' ]
])
->assertJsonStructure([
'*' => ['id', 'body', 'title', 'created_at', 'updated_at'],
]);
}
}