Summer Sale! All accounts are 50% off this week.

phpMick's avatar
Level 15

TDD for event listener.

Ok, my event is getting fired.

How on earth do I then test the listener is doing what it should?

Thanks,

Mick

0 likes
14 replies
skliche's avatar

What is your listener doing? If it's modifying your database, check that. If it is sending an email, use mail fake to test that the correct mail would have been sent.

Example for testing that the correct mail has been sent

Mail::fake();

// do whatever triggers the event

Mail::assertSent(PasswordResetToken::class, function(Mailable $mail) use ($user) {
    $mail->build(); // necessary to get the subject and body

    return $mail->hasTo($user->email) &&
            $mail->subject === trans('resetpassword.mail.subject');
});
phpMick's avatar
Level 15

That certainly sounds like the correct strategy.

Funny thing is that my event is firing but the listener is not getting called (when running tests).

When the application runs, the listener gets triggered correctly.

I wonder if this is phpUnit configuration?

Mick

tykus's avatar

Are you using Event::fake() in the existing tests?

phpMick's avatar
Level 15

Nope I'm just doing the POST which calls the code which triggers the event.

public function create_user_fires_event()
    {
        $user = $this->signInMasterAdmin();

        $customer = $this->createAndSelectChildCustomer($user);

        $newUser = $this->createUser($user);

        $this->expectsEvents(UserCreated::class);

        $this->POST(route('users.store'),$newUser);


    }
phpMick's avatar
Level 15

Brilliant, thanks!

I have now written my next test, which does not have

$this->expectsEvents(UserCreated::class); and the listener is getting called.

Thanks for your help, you have saved me a lot of time.

1 like
phpMick's avatar
Level 15

This is the finished test:

/**
     * @test
     * @throws \Exception
     */
    public function create_user_sends_email()
    {
        Mail::fake();

        $user = $this->signInMasterAdmin();

        $customer = $this->createAndSelectChildCustomer($user);

        $newUser = $this->createUser($user);

        $this->POST(route('users.store'),$newUser);

        Mail::assertSent(NewUser::class, function ($mail) use ($newUser) {
            return $mail->hasTo($newUser['email']);
        });

    }

tykus's avatar

$newUser = $this->createUser($user); is creating a record in the database; or using the factory make method??

phpMick's avatar
Level 15

It's named incorrectly, it is just making an array.

Not sure why I'm not using a factory, I will revisit that.

Thanks.

tykus's avatar

Yeah, you might wonder about what it is doing in the future. FWIW, I don't use a factory either to create the contents of a POST request; instead many of my tests will have a private method called validParams which represents a valid POST request payload; so it represents the form (rather than a User object in the database - which might have many more attributes than are being sent in the form):

private function validParams($overrides = [])
{
    return array_merge([
        'name' => 'John',
        'email' => '[email protected]',
    ], $overrides);
}

The $overrides parameter allows me to specify alternatives to the defaults, e.g.

$this->POST(route('users.store'), $this->validParams(['name' => 'Tykus']));
phpMick's avatar
Level 15

Swapped it for a factory make, then toArray().

tykus's avatar

Cool. But just keep in mind that the factory is intended to represent a Model instance, not a form... there may be more fields/attributes in the array than would be in the form you are putting under test; you might see a mass-assignment exception or two depending on how that has been set up on the model.

1 like
kunaldodiya's avatar

User timeout function to delay the event test, use testing block inside timeout and it will work

Please or to participate in this conversation.