I am writing some unit tests to test a database transaction middleware, on an exception everything within the transaction should do a rollback. And This piece of code works perfectly fine and passes the unit test:
Unit test method that succeeds
public function testTransactionShouldRollback()
{
Event::fake();
// Ignore the exception so the test itself can continue.
$this->expectException('Exception');
$this->middleware->handle($this->request, function () {
throw new Exception('Transaction should fail');
});
Event::assertDispatched(TransactionRolledBack::class);
}
Yet whenever I test a TransactionBeginning
event it fails to assert the event has been dispatched.
Unit test method that fails
public function testTransactionShouldBegin()
{
Event::fake();
$this->middleware->handle($this->request, function () {
return $this->response;
});
Event::assertDispatched(TransactionBeginning::class);
}
The actual middleware
public function handle($request, Closure $next)
{
DB::beginTransaction();
try {
$response = $next($request);
if ($response->exception) {
throw $response->exception;
}
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
if (!$response->exception) {
DB::commit();
}
return $response;
}
All transaction events fire off events so DB::beginTransaction, DB::rollBack, DB::commit
should all fire events. Yet When I am testing I only even see the transaction rollback event firing.
Is there a reason why the other events are not firing in this case and my assertDispatched is failing?
I don't know the exact reason (would have to dig deeper) but I found solution how to fix this.
It seems somehow default event dispatcher is still used here, so when you run Event::fake()
database connection uses default dispatcher. Solution is instead of running just:
Event::fake();
to run:
$fake = Event::fake();
DB::setEventDispatcher($fake);
After such modification tests for me are running fine. Below there is full test case class:
<?php
namespace Tests\Feature;
use App\Http\Middleware\TestMiddleware;
use Exception;
use Illuminate\Database\Events\TransactionBeginning;
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Database\Events\TransactionRolledBack;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* @var \App\Http\Middleware\TestMiddleware
*/
protected $middleware;
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* @var \Illuminate\Http\Response
*/
protected $response;
public function setUp():void
{
parent::setUp();
Event::fake();
$this->middleware = new TestMiddleware();
$this->request = new Request();
$this->response = new Response();
}
public function testTransactionShouldRollback()
{
$fake = Event::fake();
DB::setEventDispatcher($fake);
// Ignore the exception so the test itself can continue.
$this->expectException('Exception');
$this->middleware->handle($this->request, function () {
throw new Exception('Transaction should fail');
});
Event::assertDispatched(TransactionBeginning::class);
Event::assertDispatched(TransactionRolledBack::class);
Event::asserNotDispatched(TransactionCommitted::class);
}
public function testTransactionShouldBegin()
{
$fake = Event::fake();
DB::setEventDispatcher($fake);
$this->middleware->handle($this->request, function () {
return $this->response;
});
Event::assertDispatched(TransactionBeginning::class);
Event::assertNotDispatched(TransactionRolledBack::class);
Event::assertDispatched(TransactionCommitted::class);
}
}