Тесты для слушателей событий, отправляющих электронную почту


Я совсем новичок в тестировании, поэтому буду признателен за любую помощь. Во-первых, это мой код в каталоге приложений (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);
        });
}
Author: Lykos, 2018-01-26

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; 
});

Технически они не совсем квалифицируются как модульные тесты, потому что они попадают в базу данных, но они должны адекватно охватывать контроллер и событие.

Чтобы сделать их настоящими модульными тестами, вам потребуется реализовать шаблон репозитория для базы данных запросы, а не использовать Красноречивый напрямую и издеваться над репозиторием, чтобы вы могли утверждать, что издевательский репозиторий получает правильные данные и возвращает макет модели. Издевательство было бы полезно для этого.

 1
Author: Matthew Daly, 2018-01-26 21:43:04

Вы не запускаете событие 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;

}
 1
Author: Mike Miller, 2018-01-26 15:46:44