Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event::assertDispatched not asserting anything #18923

Closed
chrisblackwell opened this issue Apr 24, 2017 · 19 comments
Closed

Event::assertDispatched not asserting anything #18923

chrisblackwell opened this issue Apr 24, 2017 · 19 comments

Comments

@chrisblackwell
Copy link

  • Laravel Version: 5.4.19
  • PHP Version: 7.1.3
  • Database Driver & Version: Sqlite in memorey

Description:

When I try to use the Event::fake() and call the Event::assertDispatched I get a risky test warning in PHPUnit, saying nothing was asserted.

Steps To Reproduce:

I'm running a basic test method for testing my Event:

/** @test */
public function prospect_was_created_event_fired()
{
    Event::fake();

    $prospect = factory(Prospect::class)->create();

    Event::assertDispatched(ProspectWasCreated::class, function ($event) use ($prospect) {
        return $event->customer->first_name === $prospect->customer->first_name;
    });
}

and in my Prospect model, I just use the $events property:

protected $events = [
    'created' => ProspectWasCreated::class,
];
@m1guelpf
Copy link
Contributor

@chrisblackwell Could it be related with #18774?

@mlantz
Copy link
Contributor

mlantz commented Apr 24, 2017

It may be because the event fake uses a dispatcher whose listen method does nothing so its unable to speculate that the eloquent.created event has had something registered. But I'm not sure, still digging.

@sebdesign
Copy link
Contributor

sebdesign commented May 4, 2017

I'm experiencing the same issue with a custom model event for the eloquent.updating event.

Description:

Some model events are dispatched with the fire method while others are dispatched with the until method.

The EventFake does not implement the until method, therefore it does not know that those events were actually fired, so the failing assertion is a false negative.

Steps To Reproduce:

Employee model

class Employee extends Model
{
    protected $events = [
        'updating' => \App\Events\EmployeeUpdate::class,
    ];
}

Employee test

class EmployeeTest
{
    public function test_update()
    {
        Event::fake();

        $employee = factory(Employee::class)->create();
        $employee->update(['name' => 'John']);

        Event::assertDispatched(\App\Events\EmployeeUpdate::class);
    }
}

The events array of the EventFake contains the following keys:

"eloquent.booting: App\Employee",
"eloquent.booted: App\Employee",
"eloquent.created: App\Employee",
"eloquent.saved: App\Employee",
"eloquent.updated: App\Employee",
"eloquent.saved: App\Employee",

The eloquent.creating, eloquent.updating, App\Events\EmployeeUpdate, eloquent.saving are missing from the events array and the test fails with The expected [App\Events\EmployeeUpdate] event was not dispatched.

How to fix:

The until method of the EventFake could be implemented like the actual event dispatcher:

class EventFake implements Dispatcher
{
    public function until($event, $payload = [])
    {
        return $this->dispatch($event, $payload, true);
    }
}

@chrisblackwell
Copy link
Author

I have confirmed the Event::fake() doesn't work properly. I have created a new Laravel app with a single test that checks the event - https://github.com/chrisblackwell/Laravel-Event-Testing

@sebdesign
Copy link
Contributor

@chrisblackwell You haven't pointed the UserCreated class in your events array on the User model. Also you need to import the User model class in the UserCreated class.

@sebdesign
Copy link
Contributor

Also, the created event is dispatched using the fire method on the dispatcher, which I believe is working as expected.

The UserCreated model should be UserCreating and pointing to the creating event which is dispatched with the until method.

@chrisblackwell
Copy link
Author

@sebdesign I think you're missing the context here. I'm not trying to pickup a Laravel event, I'm asserting that an event fired. Forgetting the missing import in the UserCreated class would give me an error. I would love to see and error. Instead I get a assertFalse is true.

Also, I believe that event(new UserCreated($user)) is equivilent to Event::fire(new UserCreated($user))

@sebdesign
Copy link
Contributor

@chrisblackwell My mistake, I didn't pay attention to the web route.

In your feature test, $user->id will be null because that instance hasn't been saved yet. Although $e->user->id would have a real value if the App\Events\UserCreated event would be successfully dispatched. But the assertion would still fail because the two ids wouldn't be equal.

@chrisblackwell
Copy link
Author

@sebdesign It's not event getting to that point though. If you remove the entire line of return $e->user->id === $user->id; and replace with return true, you'll still get an error saying:

The expected [App\Events\UserCreated] event was not dispatched.
Failed asserting that false is true.

@sebdesign
Copy link
Contributor

Isn't that assertion failing because you are dispatching a UserCreated (which should cause an exception) instead of a App\Events\UserCreated event?

sebdesign added a commit to sebdesign/framework that referenced this issue May 4, 2017
When dispatching events using the `until` method instead of the
`dispatch` or `fire` methods, the given events were not being collected
in the `events` array of the `EventFake` class.

This was resulting false negatives, e.g. `assertDispatched` is failing
because the `until` method is not implemented like the `fire` and
`dispatch` methods.

* Fixes laravel#18923
@chrisblackwell
Copy link
Author

@sebdesign No, I'm importing the full namespaced path above. Why would it through an exception?

@sebdesign
Copy link
Contributor

@chrisblackwell You do import the correct FQCN in your test, but not in your routes file.
In fact, you could check the http status in the $response from $this->post() call in your test.

You model is successfully created in the database, but then an exception is thrown because of the missing event class being dispatched.

@mlantz
Copy link
Contributor

mlantz commented May 5, 2017

Add in the routes/web.php:
use App\Events\UserCreated;

Without this line if I try to dd($this->events) in the EventFake dispatched method I get an error statement - not sure why though

Then add to App/User.php:

protected $events = [
    'created' => UserCreated::class,
];

Then your test:

Event::fake();

$user = factory(User::class)->make(['id' => 1]);
$this->post('users', ['name' => $user->name, 'email' => $user->email, 'password' => 'pajsdalfsaf']);
$this->assertDatabaseHas('users', [
    'email' => $user->email
]);

Event::assertDispatched(UserCreated::class, function ($e) use ($user) {
    return $e->user->id === $user->id;
});

for the test to get success - seems like you need to register the event with the model since the dispatcher compares the event name to the keys of the listed events - then it looks like default factory doesn't add an ID to the user since its a make rather than a create make just spins up an example of the data set - create will publish to the db.

@sebdesign
Copy link
Contributor

sebdesign commented May 5, 2017

@mlantz thanks for your insight!

The created model event is being fired with the $halt set to false, which uses the fire method of the EventFake.

If you specify the UserCreated event to the creating event, that is being fired with the $halt set to true, which uses the until method of the EventFake. The problem is that the until method is not implemented, so the event doesn't get pushed to the $this->events array.

taylorotwell pushed a commit that referenced this issue May 5, 2017
When dispatching events using the `until` method instead of the
`dispatch` or `fire` methods, the given events were not being collected
in the `events` array of the `EventFake` class.

This was resulting false negatives, e.g. `assertDispatched` is failing
because the `until` method is not implemented like the `fire` and
`dispatch` methods.

* Fixes #18923
@aslamsayyed
Copy link

Is it working for anyone ? Its not for me in 8.8.0

@moeenakhtar
Copy link

no not working for me

@SaeedNikmehr
Copy link
Contributor

it's still not working for me in 9.3
I use EventClass::dispatch($inputs); in my controller but Event::assertDispatched( EventClass::class ); in tests seems does not get it
I get the same error as above: The expected [App\Events\EventClass] event was not dispatched.

@benjam-es
Copy link
Contributor

This is still an issue.

The answer here partially helped https://stackoverflow.com/questions/59861472/laravel-event-dispatch-assertion-fails

However, when I used the following code in place of Event::fake()

$fake = Event::fake();
DB::setEventDispatcher($fake);

It showed me a different error elsewhere in my test. Once I fixed the error, I was able to switch back to just using Event::fake() so I think the bug here in Laravel is that Event::fake() assertions don't work when there is an error elsewhere in the test, and those errors don't show.

@dannypritchard
Copy link

For anyone coming to this in >currentyear, you need to pass the event class you want to check was dispatched into Event::fake(); i.e.

Event::fake(YourEventName::class);
Event::assertDispatched(YourEventName::class);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants