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

时间:2018-01-26 13:15:11

标签: php laravel laravel-5 phpunit 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));
    }
}

现在,我正在尝试为触发要发送的电子邮件的事件和侦听器编写一些测试,因此我在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'     => '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);
        });
}

2 个答案:

答案 0 :(得分:2)

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

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

测试控制器方法

控制器方法做了三件事:

  • 返回回复
  • 创建模型实例
  • 点火活动

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

首先我们假装事件门面:

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 facade。第一步是伪造邮件外观:

Mail::fake();

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

$foo = factory(Foo::class)->create([]);

然后,触发您的活动:

event(new FooCreated($foo));

最后,断言邮件外观接收到具有适当参数的请求:

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

从技术上讲,这些都不具备单元测试的条件,因为它们可以访问数据库,但它们应该充分覆盖控制器和事件。

要使它们成为真正的单元测试,您需要为数据库查询实现存储库模式,而不是直接使用Eloquent并模拟存储库,因此您可以声明模拟的存储库接收正确的数据并拥有它返回模型的模拟。 Mockery对此有用。

答案 1 :(得分:1)

您没有触发事件FooEventFooCreated并且您没有调用实际调度事件的控制器上的方法(至少不在您显示的代码中)。

// 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;

}
相关问题