Php 在电子邮件中发送事件侦听器的测试

Php 在电子邮件中发送事件侦听器的测试,php,laravel,laravel-5,phpunit,laravel-5.5,Php,Laravel,Laravel 5,Phpunit,Laravel 5.5,我对测试很陌生,所以任何帮助都将不胜感激。首先,这是我在应用程序目录中的代码(Laravel 5.5) 现在,我正试图为触发发送电子邮件的事件和侦听器编写一些测试,因此我在unit/ExampleTest.php中创建了一个方法 namespace Tests\Unit; use Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; use App\Foo; use Illuminate\Support\Faca

我对测试很陌生,所以任何帮助都将不胜感激。首先,这是我在应用程序目录中的代码(Laravel 5.5)

现在,我正试图为触发发送电子邮件的事件和侦听器编写一些测试,因此我在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
但是,当我运行此测试时,失败,错误
,预期的[App\Events\FooEvent]事件未调度。断言false为true失败。

如何修复要通过的事件的测试以及发送电子邮件的测试

提前谢谢

更新

我已经设法添加了一个在控制器上触发事件的测试,但是我需要一个检查电子邮件是否被触发的测试

public function testStore()
{
        Event::fake();

        $this->post(route('foo.store'), [
            'full_name' => 'John Doe',
            'email'     => 'johndoe@example.com',
            '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'     => 'johndoe@example.com',
            '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);
        });
}

您没有触发事件
FooEvent
FooCreated
,也没有在实际调度事件的控制器上调用方法(至少在您显示的代码中没有)

如果您不想一直触发事件,请添加新方法:

// Foo model
public static function createWithEvent(array $attributes = [])
{
    $model = parent::create($attributes);

    event(new FooCreated($foo));

    return $model;

}

正如评论中提到的,我的建议是为控制器编写一个测试,为事件侦听器编写另一个测试。因为最终您不知道该事件是否会在将来从该控制器中删除,所以隔离测试控制器和侦听器类是有意义的

以下是我将如何测试这些类:

测试控制器方法 控制器方法完成三件事:

  • 回复
  • 创建一个模型实例
  • 触发事件
因此,我们需要将所有外部依赖项传递给它,并检查它是否执行了所需的操作

首先,我们假装事件外观:

Event::fake();
下一步是创建
illumb\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();
然后,创建模型的一个实例-您可以使用Eloquent,但使用factory通常更方便:

$foo = factory(Foo::class)->create([]);
然后,触发您的事件:

event(new FooCreated($foo));
最后,断言mail facade收到了带有适当参数的请求:

Mail::assertSent(MyEmail::class, function ($mail) use ($foo) { 
    return $mail->foo->id == $foo->id; 
});
从技术上讲,这些测试并不完全符合单元测试的条件,因为它们会访问数据库,但它们应该充分涵盖控制器和事件


为了使它们成为真正的单元测试,您需要为数据库查询实现存储库模式,而不是直接使用Eloquent并模拟存储库,这样您就可以断言模拟的存储库接收正确的数据,并让它返回模型的模拟。这将非常有用。

在哪里定义了
FooEvent
类?似乎应该改为
FooCreated
。抱歉,我已更新了代码,拼写错误,但错误仍然存在,尽管事件是从控制器的
store()
方法触发的。但是在测试中,您需要调用模型的
create()
方法。我认为没有调用
store()
方法。事件位于controller方法
store()
中,而不在
Foo::create()
中,因此基本上我在db中插入新记录,然后触发事件发送电子邮件。事件位于controller方法内部。我不能有一个测试方法作为单独的单元来测试这个事件吗?或者作为一项功能?你最好保持你的控制器简洁,在模型中包含逻辑并进行测试。否则,您需要模拟请求并测试控制器方法(您没有这样做,因此没有看到事件)我希望将事件保留在控制器中的原因是,我还在其他地方使用
Foo::create
,我不想在此时触发事件。只需向模型添加一个新方法即可then@Lykos我做了一个编辑来说明这是多么的简单!这是一个很好的解释与实际例子。它强化了单元测试应该围绕最小实用单元进行的方法。
event(new FooCreated($foo));
Mail::assertSent(MyEmail::class, function ($mail) use ($foo) { 
    return $mail->foo->id == $foo->id; 
});