Тесты для слушателей событий, отправляющих электронную почту
Я совсем новичок в тестировании, поэтому буду признателен за любую помощь. Во-первых, это мой код в каталоге приложений (Laravel 5.5)
// controller
public function store(Request $request)
{
$foo = new Foo($request->only([
'email',
'value 2',
'value 3',
]));
$foo->save();
event(new FooCreated($foo));
return redirect()->route('/');
}
// Events/FooCreated
use App\Foo;
class FooCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $foo;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
}
// Listeners/
use App\Events\FooCreated;
use App\Mail\FooSendingEmail;
class EmailSendListener
{
/**
* Handle the event.
*
* @param EnquiryWasCreated $event
* @return void
*/
public function handle(FooCreated $event)
{
\Mail::to($event->foo->email)->send(new FooSendingEmail($event->foo));
}
}
Теперь я пытаюсь написать несколько тестов для события и прослушивателя, который запускает отправку электронной почты, поэтому я создал метод в unit/ExampleTest.php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Foo;
use Illuminate\Support\Facades\Event;
use App\Events\FooCreated;
class ExampleTest extends TestCase
{
use RefreshDatabase;
public function testEventTriggered(){
Event::fake();
$foo = factory(\App\Foo::class)->create();
Event::assertDispatched(FooCreated::class, function($event) use ($foo){
return $event->foo->id == $foo->id;
});
}
}
// assume similar this applies for emails according to docs https://laravel.com/docs/5.5/mocking#mail-fake
Но когда я запускаю этот тест, происходит сбой с ошибкой The expected [App\Events\FooEvent] event was not dispatched. Failed asserting that false is true.
Как я могу исправить события testfor для прохождения, а также для проверки отправки электронной почты?
Спасибо в продвижение.
ОБНОВЛЕНИЕ
Мне удалось добавить тест для запуска события на контроллере, но мне нужен тест для проверки срабатывания электронных писем
public function testStore()
{
Event::fake();
$this->post(route('foo.store'), [
'full_name' => 'John Doe',
'email' => '[email protected]',
'body' => 'Lorem ipsum dolor sit amet',
]);
$foo = Foo::first();
Event::assertDispatched(FooCreated::class, function ($event) use ($foo) {
return $event->foo->id === $foo->id;
});
}
public function testEmailSent()
{
Mail::fake();
// similar to prevous one in order to fire the event
$this->post(route('foo.store'), [
'full_name' => 'John Doe',
'email' => '[email protected]',
'body' => 'Lorem ipsum dolor sit amet',
'reference_code' => str_random(25),
]);
$foo = Foo::first();
Mail::assertSent(FooSendingEmail::class, function ($mail) use ($foo) {
return $mail->hasTo($foo->email);
});
}
2 answers
Как упоминалось в комментариях, я бы посоветовал написать один тест для контроллера, а другой для прослушивателя событий. Поскольку в конечном счете вы не знаете, может ли это событие быть удалено с этого контроллера в будущем, имеет смысл тестировать классы контроллера и прослушивателя изолированно.
Вот как я бы проверил эти классы:
Тестирование метода контроллера
Метод контроллера выполняет три действия:
- Возвращать ответ
- Создайте экземпляр модели
- Запуск события
Поэтому нам необходимо передать ему все его внешние зависимости и проверить, выполняет ли он необходимые действия.
Сначала мы подделываем фасад события:
Event::fake();
Следующим шагом является создание экземпляра Illuminate\Http\Request
для представления HTTP-запроса, переданного контроллеру:
$request = Request::create('/store', 'POST',[
'foo' => 'bar'
]);
Если вы используете пользовательский класс запроса формы, вы должны создать его экземпляр точно таким же образом.
Затем, создайте экземпляр контроллера и вызовите метод, передав ему объект запроса:
$controller = new MyController();
$response = $controller->store($request);
Имеет смысл затем проверить реакцию контроллера. Вы можете проверить код состояния следующим образом:
$this->assertEquals(302, $response->getStatusCode());
Вы также можете проверить, соответствует ли содержание ответа тому, что вы ожидаете увидеть.
Затем вы захотите получить вновь созданный экземпляр модели и убедиться, что он существует:
$foo = Foo::where('foo', 'bar')->first();
$this->assertNotNull($foo);
Вы также можете использовать assertEquals()
для проверки атрибутов модели, если подходящий. Наконец, вы проверяете, что событие было запущено:
Event::assertDispatched(FooWasCreated::class, function ($event) use ($foo) {
return $event->foo->id === $foo->id;
});
Этот тест не должен касаться какой-либо функциональности, вызванной событием, только то, что событие запускается.
Тестирование прослушивателя событий
Поскольку все, что делает прослушиватель событий, - это отправляет электронное письмо при передаче события, мы должны проверить, вызывает ли он почтовый фасад с правильными аргументами. Первый шаг - подделать почтовый фасад:
Mail::fake();
Затем создайте экземпляр своей модели - вы можно использовать красноречивый, но обычно удобнее использовать фабрику:
$foo = factory(Foo::class)->create([]);
Затем запустите свое событие:
event(new FooCreated($foo));
Наконец, подтвердите, что почтовый фасад получил запрос с соответствующими аргументами:
Mail::assertSent(MyEmail::class, function ($mail) use ($foo) {
return $mail->foo->id == $foo->id;
});
Технически они не совсем квалифицируются как модульные тесты, потому что они попадают в базу данных, но они должны адекватно охватывать контроллер и событие.
Чтобы сделать их настоящими модульными тестами, вам потребуется реализовать шаблон репозитория для базы данных запросы, а не использовать Красноречивый напрямую и издеваться над репозиторием, чтобы вы могли утверждать, что издевательский репозиторий получает правильные данные и возвращает макет модели. Издевательство было бы полезно для этого.
Вы не запускаете событие FooEvent
его FooCreated
, и вы не вызываете метод на контроллере, который фактически отправляет событие (по крайней мере, не в коде, который вы показываете).
// controller
public function store(Request $request)
{
$foo = Foo::create($request->only([
'email',
'value 2',
'value 3',
]));
return redirect()->route('/');
}
// Foo model
public static function create(array $attributes = [])
{
$model = parent::create($attributes);
event(new FooCreated($foo));
return $model;
}
///test
public function testEventTriggered()
{
$foo = factory(\App\Foo::class)->create();
Event::fake();
Event::assertDispatched(FooCreated::class, function($event) use ($foo){
return $event->foo->id == $foo->id;
});
}
Или добавьте новый метод, если вы не хотите запускать свое событие все время:
// Foo model
public static function createWithEvent(array $attributes = [])
{
$model = parent::create($attributes);
event(new FooCreated($foo));
return $model;
}