laravel api autentikacija

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.

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 resurse
  • POST: kreiranje resursa
  • PUT: Ažurirajte resurse
  • DELETE: Izbrišite resurse
HTTP metode predstavljaju akcije za Laravel API autentifikaciju
HTTP metode predstavljaju akcije

Akcija ažuriranja: PUT vs. POŠTA

Što se tiče API RESTful Postoje različita mišljenja o tome da li nadograditi sa POSTPATCHili 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 RFCPUT 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 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>
Laravel server
Laravel je instaliran

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()down() izvode se odnosno tokom migrate i bilo koji rollback;
  • $table->increments('id') postavlja cjelobrojno polje koje se automatski povećava tako što ga imenuje id;
  • $table->timestamps() će postaviti polja created_atupdated_at tipa timestamp. Ne moramo da brinemo o postavljanju podrazumevane vrednosti, jer Laravel 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 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 create() e update() di Elokventan. Također možete koristiti nekretninu $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. Laravel 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 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 za storeakcije.
  • 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:

NotFoundHttpException Stacktrace

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 nameemailpassword 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:

Stacktrace InvalidArgumentException

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 sqlitesa :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 TokenGuardper 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'],
            ]);
    }

}

autor