0

I am working with an application built on Laravel 11. I have a Survey model with the following public function:

public function finish(): void
{
    $this->update([
        'finished_at' => Carbon::now(),
    ]);

    event(new SurveyFinishedEvent($this));
}

The SurveyFinishedEvents triggers a listener that makes API calls. When running unit tests, I would rather not go to the effort of setting up the API mocking in every test that involves calling this function so I am trying to suppress events. Here is an example:

/**
 * @covers finish
 */
public function test_finish_whenEventsSuppressed_thenNoEventsDispatched(): void
{
    // Arrange.
    $survey = Survey::factory()->create();

    Event::fake();

    // Act.
    Survey::withoutEvents(function () use ($survey) {
        $survey->finish();
    });

    // Assert.
    Event::assertNotDispatched(SurveyFinishedEvent::class);
}

This fails. withoutEvents is failing to prevent the dispatch of the SurveyFinishedEvent. Am I misunderstanding the purpose of withoutEvents?

I have considered registering the event on the model via the $dispatchesEvents variable, but I do not want the event to be triggered every time the survey is updated, only when the finished_at date is updated.

2 Answers 2

0

I believe withoutEvents are used for model events for the following method.

app\Models\Survey.php

protected static function booted()
{
    static::creating(function ($user) {
        // Code before creating
    });

    static::created(function ($user) {
        // Code after created
    });

    static::updating(function ($user) {
       // Code before update
    });

    static::updated(function ($user) {
        // This will not be fired when doing withoutModels
        Log::info('Suvey model updated event fired');
    });
}
$survey = Survey::first()->update(['name' => 'Updated Survey']); // The "Survey model updated event fired" will be logged.

If you do not want these events to be fired, the withoutEvents is used.

Survey::withoutEvents(function () {
    $survey = Survey::create(['name' => 'My Survey']);

    $survey->update(['name' => 'Updated survey name']); // verify that your log doesn't contain the updated logs.
});

Now when you dispatch the event manually with event(new SomeEvent()), this is not part of the model events. You are manually firing it.

In the test case, Event::fake(); simulates the event dispatch. This means that your actual event won't be fired, but the assert method should be:

// Assert.
Event::assertDispatched(SurveyFinishedEvent::class);

This will pass the test case, and will not actually handle your event.

Sign up to request clarification or add additional context in comments.

Comments

0

Model::withoutEvents() is used to suppress the Eloquent model events from firing (creating, created, updating, updated, etc). Your SurveyFinishedEvent event is not an Eloquent model event, but a standard event that you are manually dispatching in your finish() method.

That said, Event::fake() is already doing what you want to do. It is preventing the Event from actually firing, but also lets you test that the event should have been fired. If you change your assert to assert that the event was dispatched, then you get the test coverage that your finish() method does indeed dispatch the event.

So, your test should something like:

/**
 * @covers finish
 */
public function test_finish_whenEventsSuppressed_thenNoEventsDispatched(): void
{
    // Arrange.
    $survey = Survey::factory()->create();

    // Disable events.
    Event::fake();

    // Act.
    $survey->finish();

    // Assert the event would have been fired.
    Event::assertDispatched(SurveyFinishedEvent::class);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.