I'm quite new to testing so any help would be appreciated. First things first, this is my code inside App directory (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));
}
}
Now, I'm trying to write some tests for the event and the listener that triggers the email to sent, so I have created a method in 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
but when I run this test, fails with error The expected [App\Events\FooEvent] event was not dispatched. Failed asserting that false is true.
How can I fix the testfor events to pass and also to test sending email ?
Thanks in advance.
UPDATE
I have managed to add a test for firing the event on controller, but I 'd need a test for checking the emails are triggered
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);
});
}
As mentioned in the comments, my advice would be to write one test for the controller, and another for the event listener. Since ultimately you don't know if that event may be removed from that controller in future, it makes sense to test the controller and listener classes in isolation.
Here's how I would test these classes:
The controller method does three things:
We therefore need to pass it all its external dependencies and check it carries out the required actions.
First we fake the event facade:
Event::fake();
Next step is to create an instance of Illuminate\Http\Request
to represent the HTTP request passed to the controller:
$request = Request::create('/store', 'POST',[
'foo' => 'bar'
]);
If you're using a custom form request class, you should instantiate that in exactly the same way.
Then, instantiate the controller, and call the method, passing it the request object:
$controller = new MyController();
$response = $controller->store($request);
It makes sense to then test the response from the controller. You can test the status code like this:
$this->assertEquals(302, $response->getStatusCode());
You may also want to check the content of the response matches what you expect to see.
You will then want to retrieve the newly created model instance, and verify it exists:
$foo = Foo::where('foo', 'bar')->first();
$this->assertNotNull($foo);
You can also use assertEquals()
to check the attributes on the model if appropriate. Finally, you check the event was fired:
Event::assertDispatched(FooWasCreated::class, function ($event) use ($foo) {
return $event->foo->id === $foo->id;
});
This test should not concern itself with any functionality triggered by the event, only that the event gets triggered.
Since all the event listener does is send an email when passed an event, we should test that it calls the Mail facade with the correct arguments. The first step is to fake the mail facade:
Mail::fake();
Then, create an instance of your model - you can use Eloquent but it's usually more convenient to use a factory:
$foo = factory(Foo::class)->create([]);
Then, trigger your event:
event(new FooCreated($foo));
Finally, assert the mail facade received a request with the appropriate arguments:
Mail::assertSent(MyEmail::class, function ($mail) use ($foo) {
return $mail->foo->id == $foo->id;
});
Technically, these don't quite qualify as being unit tests because they hit the database, but they should cover the controller and event adequately.
To make them true unit tests, you'd need to implement the repository pattern for the database queries rather than using Eloquent directly and mock the repository, so you can assert that the mocked repository receive the right data and have it return a mock of the model. Mockery would be useful for this.
You arent firing the event FooEvent
its FooCreated
and you are not calling the method on the controller that actually dispatches the event (at least not in the code you are showing).
// 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;
});
}
Or add a new method if you don't want to fire your event all the time:
// Foo model
public static function createWithEvent(array $attributes = [])
{
$model = parent::create($attributes);
event(new FooCreated($foo));
return $model;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With