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

ltd*_*dev 4 php phpunit laravel laravel-5 laravel-5.5

我对测试很陌生,所以任何帮助将不胜感激。首先,这是我在 App 目录中的代码(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));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我正在尝试为事件和触发电子邮件发送的侦听器编写一些测试,因此我在 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
Run Code Online (Sandbox Code Playgroud)

但是当我运行这个测试时,失败并出现错误 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'     => '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);
        });
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*aly 8

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

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

测试控制器方法

控制器方法做了三件事:

  • 返回响应
  • 创建模型实例
  • 触发事件

因此,我们需要将其所有外部依赖项传递给它并检查它是否执行所需的操作。

首先我们伪造事件门面:

Event::fake();
Run Code Online (Sandbox Code Playgroud)

下一步是创建一个实例Illuminate\Http\Request来表示传递给控制器​​的 HTTP 请求:

$request = Request::create('/store', 'POST',[
    'foo' => 'bar'
]);
Run Code Online (Sandbox Code Playgroud)

如果您使用的是自定义表单请求类,则应以完全相同的方式对其进行实例化。

然后,实例化控制器,并调用该方法,将请求对象传递给它:

$controller = new MyController();
$response = $controller->store($request);
Run Code Online (Sandbox Code Playgroud)

然后测试来自控制器的响应是有意义的。您可以像这样测试状态代码:

$this->assertEquals(302, $response->getStatusCode());
Run Code Online (Sandbox Code Playgroud)

您可能还想检查响应的内容是否与您希望看到的内容相匹配。

然后,您需要检索新创建的模型实例,并验证它是否存在:

$foo = Foo::where('foo', 'bar')->first();
$this->assertNotNull($foo);
Run Code Online (Sandbox Code Playgroud)

assertEquals()如果合适,您还可以使用来检查模型上的属性。最后,您检查事件是否被触发:

Event::assertDispatched(FooWasCreated::class, function ($event) use ($foo) { 
    return $event->foo->id === $foo->id; 
});
Run Code Online (Sandbox Code Playgroud)

这个测试不应该关心由事件触发的任何功能,只关心事件被触发。

测试事件监听器

由于事件侦听器所做的只是在传递事件时发送电子邮件,我们应该测试它是否使用正确的参数调用了 Mail 门面。第一步是伪造邮件外观:

Mail::fake();
Run Code Online (Sandbox Code Playgroud)

然后,创建模型的实例 - 您可以使用 Eloquent,但使用工厂通常更方便:

$foo = factory(Foo::class)->create([]);
Run Code Online (Sandbox Code Playgroud)

然后,触发您的事件:

event(new FooCreated($foo));
Run Code Online (Sandbox Code Playgroud)

最后,断言邮件门面收到了带有适当参数的请求:

Mail::assertSent(MyEmail::class, function ($mail) use ($foo) { 
    return $mail->foo->id == $foo->id; 
});
Run Code Online (Sandbox Code Playgroud)

从技术上讲,这些并不完全符合单元测试的要求,因为它们会访问数据库,但它们应该充分覆盖控制器和事件。

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