Prologo
Primi Passi
Architettura
Le Basi
- Routing
- Middleware
- Protezione da CSRF
- Controller
- Richieste
- Risposte
- Views
- Blade
- Vite
- URL
- Sessioni
- Validazione
- Errori
- Logging
Approfondimenti
- Artisan
- Broadcasting
- Cache
- Collezioni
- Concorrenza
- Contesto
- Contratti
- Eventi
- File System
- Helpers
- Client HTTP
- Localizzazione
- Notifiche
- Sviluppo di Package
- Processi
- Code
- Rate-limiting
- Stringhe
- Scheduling di Task
Sicurezza
Database
Eloquent ORM
Testing
Package
Mocking
Introduzione
Quando si testano applicazioni Laravel, potrebbe essere utile "mockare" alcuni aspetti dell’applicazione in modo che non vengano eseguiti realmente durante un determinato test. Ad esempio, quando si testa un controller che invia un evento, potrebbe essere necessario mockare gli ascoltatori dell’evento in modo che non vengano eseguiti durante il test. Questo permette di testare solo la risposta HTTP del controller senza preoccuparsi dell’esecuzione degli ascoltatori, poiché gli ascoltatori possono essere testati nel loro caso di test specifico.
Laravel offre metodi utili per mockare eventi, job e altre facades direttamente. Questi helper forniscono principalmente uno strato di comodità su Mockery, così non è necessario effettuare manualmente chiamate complesse a Mockery.
Mocking degli Oggetti
Quando effettui il mocking di un oggetto che verrà iniettato nella tua applicazione tramite il service container di Laravel, dovrai collegare la tua istanza mock al container come un binding instance
. Questo istruirà il container a utilizzare la tua istanza mock dell’oggetto invece di costruire l’oggetto stesso:
use App\Service;
use Mockery;
use Mockery\MockInterface;
test('something can be mocked', function () {
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
})
);
});
use App\Service;
use Mockery;
use Mockery\MockInterface;
public function test_something_can_be_mocked(): void
{
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
})
);
}
Per rendere questo più conveniente, puoi usare il metodo mock
fornito dalla classe base del caso di test di Laravel. Ad esempio, il seguente esempio è equivalente a quello sopra:
use App\Service;
use Mockery\MockInterface;
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
Puoi usare il metodo partialMock
quando hai bisogno di mockare solo alcuni metodi di un oggetto. I metodi che non sono mockati verranno eseguiti normalmente quando vengono chiamati:
use App\Service;
use Mockery\MockInterface;
$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
Allo stesso modo, se vuoi creare spy su un oggetto, la classe base del caso di test di Laravel offre un metodo spy
come comoda interfaccia al metodo Mockery::spy
. Gli spy sono simili ai mock; tuttavia, gli spy registrano ogni interazione tra lo spy e il codice in fase di test, permettendoti di fare asserzioni dopo che il codice è stato eseguito:
use App\Service;
$spy = $this->spy(Service::class);
// ...
$spy->shouldHaveReceived('process');
Mocking di Facades
Diversamente dalle tradizionali chiamate a metodi statici, le facade (incluse le facade in tempo reale) possono essere simulate. Questo offre un grande vantaggio rispetto ai metodi statici tradizionali e garantisce la stessa testabilità che avresti se usassi l’iniezione delle dipendenze tradizionale. Durante i test, potresti spesso voler simulare una chiamata a una facade di Laravel che si verifica in uno dei tuoi controller. Ad esempio, considera la seguente azione del controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Retrieve a list of all users of the application.
*/
public function index(): array
{
$value = Cache::get('key');
return [
// ...
];
}
}
Possiamo simulare la chiamata alla facade Cache
usando il metodo shouldReceive
, che restituirà un’istanza di un mock di Mockery. Poiché le facade sono effettivamente risolte e gestite dal service container di Laravel, hanno molta più testabilità rispetto a una classe statica tipica. Ad esempio, simuliamo la nostra chiamata al metodo get
della facade Cache
:
<?php
use Illuminate\Support\Facades\Cache;
test('get index', function () {
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
});
<?php
namespace Tests\Feature;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class UserControllerTest extends TestCase
{
public function test_get_index(): void
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
}
}
Non dovresti simulare la facade
Request
. Invece, passa i dati di input desiderati ai metodi di test HTTP comeget
epost
durante l’esecuzione del test. Allo stesso modo, invece di simulare la facadeConfig
, chiama il metodoConfig::set
nei tuoi test.
Facade Spy
Se desideri creare spy su una facade, puoi chiamare il metodo spy
sulla facade corrispondente. Gli spies sono simili ai mocks; tuttavia, registrano ogni interazione tra lo spy e il codice in fase di test, permettendoti di fare asserzioni dopo l’esecuzione del codice:
<?php
use Illuminate\Support\Facades\Cache;
test('i valori vengono memorizzati nella cache', function () {
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
});
use Illuminate\Support\Facades\Cache;
public function test_i_valori_vengono_memorizzati_nella_cache(): void
{
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
}
Interagire con il Tempo
Durante i test, potresti aver bisogno di modificare l’ora restituita da helper come now
o Illuminate\Support\Carbon::now()
. Fortunatamente, la classe di base per i test delle funzionalità di Laravel include helper che ti permettono di manipolare l’ora corrente:
test('il tempo può essere manipolato', function () {
// Viaggia nel futuro...
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Viaggia nel passato...
$this->travel(-5)->hours();
// Viaggia a un'ora specifica...
$this->travelTo(now()->subHours(6));
// Torna al tempo presente...
$this->travelBack();
});
public function test_il_tempo_può_essere_manipolato(): void
{
// Viaggia nel futuro...
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Viaggia nel passato...
$this->travel(-5)->hours();
// Viaggia a un'ora specifica...
$this->travelTo(now()->subHours(6));
// Torna al tempo presente...
$this->travelBack();
}
Puoi anche fornire una chiusura ai vari metodi di viaggio nel tempo. La chiusura verrà eseguita con il tempo congelato all’ora specificata. Una volta eseguita la chiusura, il tempo riprenderà normalmente:
$this->travel(5)->days(function () {
// Testa qualcosa cinque giorni nel futuro...
});
$this->travelTo(now()->subDays(10), function () {
// Testa qualcosa in un momento specifico...
});
Il metodo freezeTime
può essere usato per congelare l’ora attuale. Allo stesso modo, il metodo freezeSecond
congela l’ora corrente ma all’inizio del secondo attuale:
use Illuminate\Support\Carbon;
// Congela il tempo e riprende il tempo normale dopo aver eseguito la chiusura...
$this->freezeTime(function (Carbon $time) {
// ...
});
// Congela il tempo al secondo attuale e riprende il tempo normale dopo aver eseguito la chiusura...
$this->freezeSecond(function (Carbon $time) {
// ...
});
Come ti aspetteresti, tutti i metodi discussi sopra sono principalmente utili per testare il comportamento dell’applicazione sensibile al tempo, come bloccare post inattivi in un forum di discussione:
use App\Models\Thread;
test('i thread del forum si bloccano dopo una settimana di inattività', function () {
$thread = Thread::factory()->create();
$this->travel(1)->week();
expect($thread->isLockedByInactivity())->toBeTrue();
});
use App\Models\Thread;
public function test_i_thread_del_forum_si_bloccano_dopo_una_settimana_di_inattività()
{
$thread = Thread::factory()->create();
$this->travel(1)->week();
$this->assertTrue($thread->isLockedByInactivity());
}