noah1400
noah1400

Reputation: 1511

Laravel: Notification does not get sent on Demand

I am working on a project using Laravel. I want to use Events and Notifications. For context: A organization can post a Task ( Can be compared to a Job ). A User can apply for that job. As soon as a User applies for a Task an Email should be sent to the contact email stored with the Task.´

My Event: ApplicationCreated My Notification: ApplicationCreatedNotification My Listener: SendApplicationCreatedNotification

My controller function:

public function store(StoreApplicationRequest $request, Task $task)
{
    Gate::authorize('store', [Application::class, $task]);

    $application = null;

     DB::transaction(function () use ($request, $task, &$application) {
        $application = $task->applications()->create([
            ...$request->validated(),
            'user_id' => auth()->id(),
        ]);
    
        $application->load('user');
        $application->setRelation('task', $task);
     });

        

    ApplicationCreated::dispatch($application);

    return response()->json($application, 201);
}

My handle function in the Listener:

public function handle(ApplicationCreated $event): void
{
    $recipient = $event->application->task->contact_email;
    $name = $event->application->task->contact_firstname . ' ' . $event->application->task->contact_lastname;
    Notification::route('mail', [
        $recipient => $name,
    ])
        ->notify(new ApplicationCreatedNotification($event->application));

            
    // @TODO: Send notification to the applicant as well 
    //        something like this:
    //        "You successfully applied to the task: $task->title"
}

And My Test:

public function test_user_can_apply_to_task(): void
{
    $user = User::factory()->organization()->create();
    $orga = Organization::factory()->forUser($user)->create();
    $task = Task::factory()->forOrganization($orga)->create();

    $basicUser = User::factory()->create();
    Event::fake();
    Notification::fake();

    $response = $this->actingAs($basicUser)->post(route('applications.store', $task->id));

    Event::assertDispatched(ApplicationCreated::class);


    Notification::assertSentOnDemand(ApplicationCreatedNotification::class, function (ApplicationCreatedNotification $notification, array $channels, object $notifiable) use ($task) {
        $recipient = $task->contact_email;
        $name = $task->contact_firstname . ' ' . $task->contact_lastname;
        return $notifiable->routes['mail'] === [$recipient => $name];
    });

    $response->assertStatus(201);

    $this->assertDatabaseHas('applications', [
        'task_id' => $task->id,
        'user_id' => $basicUser->id,
    ]);

}

I get this output:

FAILED  Tests\Feature\CreateApplicationTest > user can apply to task
The expected [App\Notifications\ApplicationCreatedNotification] notification was not sent.
Failed asserting that false is true.

  at vendor\laravel\framework\src\Illuminate\Support\Testing\Fakes\NotificationFake.php:89
     85▕         if (is_numeric($callback)) {
     86▕             return $this->assertSentToTimes($notifiable, $notification, $callback);
     87▕         }
     88▕
  ➜  89▕         PHPUnit::assertTrue(
     90▕             $this->sent($notifiable, $notification, $callback)->count() > 0,
     91▕             "The expected [{$notification}] notification was not sent."
     92▕         );
     93▕     }

  1   vendor\laravel\framework\src\Illuminate\Support\Testing\Fakes\NotificationFake.php:89
  2   vendor\laravel\framework\src\Illuminate\Support\Testing\Fakes\NotificationFake.php:54

I know that the handle function gets called because when I add dd($recipient, $name); exit(0); the test process stops and I get this output

"[email protected]" // app\Listeners\SendApplicationCreatedNotification.php:33
"Katrin Thomas" // app\Listeners\SendApplicationCreatedNotification.php:33

Edit

After some debugging, I found out that a NotificationSent event gets triggered when executing my test. This event gets Dispatched by the NotificationSender class:

protected function sendToNotifiable($notifiable, $id, $notification, $channel)
    {
        if (! $notification->id) {
            $notification->id = $id;
        }

        if (! $this->shouldSendNotification($notifiable, $notification, $channel)) {
            return;
        }

        $response = $this->manager->driver($channel)->send($notifiable, $notification);

        $this->events->dispatch(
            new NotificationSent($notifiable, $notification, $channel, $response)
        );
    }

All fake Notifications get "send" by the NotificationFake class which implements the NotificationDispatcher which is an alias of the Dispatch interface.

It looks like when sending the notification onDemand it does not use the NotificationFake class as its dispatcher.

Does anyone has an Idea why this doesn't work?

Upvotes: 0

Views: 134

Answers (1)

noah1400
noah1400

Reputation: 1511

By reading the docs I found out that calling Event::fake() will prevent the event listener from running. Therefore the notifications will not be sent. So removing Event::fake() solves my problem.

Upvotes: 0

Related Questions