Laravel event dispatch assertion fails
Asked Answered
H

2

6

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?

Hedve answered 22/1, 2020 at 14:3 Comment(0)
C
6

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);
    }
}
Circulation answered 22/1, 2020 at 18:50 Comment(2)
It works now, but this seems like a bug. This test was running perfectly fine before. I have not checked this test in a while and have upgraded Laravel multiple times so perhaps something is going wrong in a newer version? Thanks for your time, much appreciated.Hedve
See more github.com/laravel/framework/issues/…Canicular
C
0

Using the other answer, this got things working to a point where I discovered there was an error elsewhere in the test or code.

I was able to switch back to Event::fake() when I fixed the unrelated error.

see here for further information :

https://github.com/laravel/framework/issues/18923#issuecomment-1311520190

Canicular answered 11/11, 2022 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.