Posted at

【Laravel】On-Demand Notifications をモック化してテストする

More than 1 year has passed since last update.


先に結論

次のようにしてできる。

use Illuminate\Notifications\AnonymousNotifiable;

Notification::fake();

// :
// テスト対象のロジック実行
// :

Notification::assertSentTo(
new AnonymousNotifiable(),
InvoicePaid::class, // 対象の Notification class
function($notification, $channels, $notifiable) {
return $notifiable->routes['mail'] == 'test@example.com';
}
);


前提1:On-Demand Notifications とは?

メール送信のような「通知」を行う際には通常、 Illuminate\Notifications\Notifiable trait を use した class に対して notify() メソッドを実行する。

On-Demand Notifications はそのような class が存在しない対象に対して、直接通知を行うための仕組みである。

Notification::route('mail', 'taylor@example.com')

->route('nexmo', '5555555555')
->notify(new InvoicePaid($invoice));

例えば私は、「フォームにメールアドレスだけ入力してもらって、アカウント登録フォームの URL をそのメールアドレスに送信する」という場面で使っている。1


前提2:Notifications をモック化してテスト

テストを実行では、実際にメール送信等はしたくない。

そこで、Laravel では Notifications をモック化する仕組みを提供している。

Notification::fake();

// Perform order shipping...

Notification::assertSentTo(
$user,
OrderShipped::class,
function ($notification, $channels) use ($order) {
return $notification->order->id === $order->id;
}
);

このモック化のおかげで、実際にメールを送信することなく「対象にメールが送信されること」を確かめるテストを書くことができる。


本題:On-Demand Notifications をモック化してテスト

しかし、公式ドキュメントの情報からだけでは On-Demand Notifications を使っている場合にモック化する方法がわからない。

Notification::fake();

// こんな風にしていい感じに解釈してくれないかな、と期待したが
Notification::assertSentTo(
'test@exmaple.com',
InvoicePaid::class
);

# こんなエラーで怒られる

ErrorException: get_class() expects parameter 1 to be object, string given

/var/www/app/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:165
/var/www/app/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:153
/var/www/app/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:129
/var/www/app/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php:43
/var/www/app/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:221
:(以下略)

実は解決方法は Laravel5.5 の時点で既に提示されており、

https://github.com/laravel/framework/pull/21379


再掲

use Illuminate\Notifications\AnonymousNotifiable;

Notification::fake();

// :
// テスト対象のロジック実行
// :

Notification::assertSentTo(
new AnonymousNotifiable(),
InvoicePaid::class, // 対象の Notification class
function($notification, $channels, $notifiable) {
return $notifiable->routes['mail'] == 'test@example.com';
}
);


のようにすることで、めでたくモック化してテストできる。


余談:AnonymousNotifiable とは?

On-Demand Notifications を利用するときに出てくる Notifialbe な class こそが AnonymousNotifiable


tinker

>>> \Notification::route('mail', 'test@example.com')

=> Illuminate\Notifications\AnonymousNotifiable {
+routes: [
"mail" => "test@example.com",
],
}





  1. ちなみに、Laravel 5.7 よりその機能自体が提供されたよう(https://laravel.com/docs/5.7/releases#laravel-5.7)