8

I have been trying to test events and I had it working yesterday. That was before I started to refactor the test code to keep it from being too repetitious. I added the setUp method call to generate the fake data using ModelFactories. This was done in each test case yesterday and as stated it was working.

I am thinking it has something to do with using the setUp method but I have no idea why that would be the case. First I attempted to use setUpBeforeClass() static method as that is only run once on unit test runs. However the laravel application is not actually setup until the first call to setUp()... A possible bug maybe? It is documented in this SO post Setting up PHPUnit tests in Laravel.

Therefore I opted to use the setUp method and just check to see if the static property is null or not, if it is null then it generates the data, if not it just goes on it's way.

Here is the output from running phpunit on the project

➜  project git:(laravel-5.2-testing) ✗ phpunit
PHPUnit 5.2.10 by Sebastian Bergmann and contributors.

E                                                                  1 / 1 (100%)

Time: 8.94 seconds, Memory: 33.50Mb

There was 1 error:

1) UserEmailNotificationsTest::testNotificationSentOnGroupMediaSaving
Exception: These expected events were not fired: [\App\Events\GroupMediaSaving]

/Users/jcrawford/Dropbox/Work/Viddler/Repositories/l5_media_communities/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php:44
/Users/jcrawford/Dropbox/Work/Viddler/Repositories/l5_media_communities/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:127

FAILURES!
Tests: 1, Assertions: 0, Errors: 1, Skipped: 8.

Here is my code for the unit test file that I have created.

<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class UserEmailNotificationsTest extends \TestCase
{
    use DatabaseTransactions;

    const COMMUNITIES_TO_CREATE = 3;
    const USERS_TO_CREATE = 10;
    const ADMINS_TO_CREATE = 5;

    protected static $communities = null;
    protected static $users = null;
    protected static $admins = null;

    public function setUp()
    {
        parent::setUp(); // TODO: Change the autogenerated stub

        if(is_null(self::$communities)) {
            self::$communities = factory(\Community::class, self::COMMUNITIES_TO_CREATE)->create()->each(function ($community) {
                self::$users[$community->id] = factory(User::class, self::USERS_TO_CREATE)->create()->each(function (\User $user) use ($community) {
                    $user->community()->associate($community);
                    $user->save();
                });

                self::$admins[$community->id] = factory(User::class, self::ADMINS_TO_CREATE, 'superadmin')->create()->each(function (\User $admin) use ($community) {
                    $admin->community()->associate($community);
                    $admin->save();
                });

                $community->save();
            });
        }
    }

    public static function getRandomCommunityWithAssociatedData()
    {
        $community = self::$communities[mt_rand(0, count(self::$communities)-1)];
        return ['community' => $community, 'users' => self::$users[$community->id], 'admins' => self::$admins[$community->id]];
    }

    /**
     * Test that the notification event is fired when a group media
     * item is saved.
     */
    public function testNotificationSentOnGroupMediaSaving()
    {
        $data = self::getRandomCommunityWithAssociatedData();

        // FOR SOME REASON THIS SAYS THE EVENT IS NEVER FIRED WHEN IT ACTUALLY IS FIRED.
        $this->expectsEvents(['\App\Events\GroupMediaSaving']);

        $community = $data['community'];
        $admin = $data['admins'][0];
        $user = $data['users'][0];

        $asset = factory(Asset\Video::class)->make();
        $asset->community()->associate($community);
        $asset->user()->associate($admin);
        $asset->save();

        $group = factory(Group::class)->make();
        $group->community()->associate($community);
        $group->created_by = $admin->id;
        $group->save();


        $groupMedia = factory(GroupMedia::class)->make();
        $groupMedia->asset()->associate($asset);
        $groupMedia->user()->associate($user);
        $groupMedia->group()->associate($group);
        $groupMedia->published_date = date('Y-m-d H:i:s', strtotime('-1 day'));
        $groupMedia->save();

        // I can print_r($groupMedia) here and it does have an ID attribute so it was saved, I also put some debugging in the event object and it is actually fired.....
    }
}

Any thoughts as to why it doesn't see the events being fired? I find it odd that they are fired if I create the models inside the test case but seem to be failing when done inside of setUp(). The worst part is I am not creating the GroupMedia model in the setUp method rather that is done in the test case.

I have also dumped the data that is returned from the getRandomCommunityWithAssociatedData method and it is returning proper model objects all with id attributes which tells me they were all saved to the database during creation.

As requested here is the code that is actually firing the event, it is located in the GroupMedia model in the static boot method.

protected static function boot()
{
    parent::boot();

    static::saving(function($groupMedia) {
        Event::fire(new \App\Events\GroupMediaSaving($groupMedia));
    });
}
Community
  • 1
  • 1
Joseph Crawford
  • 1,470
  • 1
  • 15
  • 29
  • I'm not sure about the static attributes (not judging, I just do it differently, [example](https://github.com/timegridio/timegrid/blob/master/tests/acceptance/scenarios/consulting/ConsultingScenarioTest.php)). Also, as per [laravel documentation](https://laravel.com/docs/master/testing) I think you should not pass an array as argument but the **Event** itself: `$this->expectsEvents('\App\Events\GroupMediaSaving');` or `$this->expectsEvents(App\Events\GroupMediaSaving::class);` – alariva Mar 25 '16 at 01:07
  • Ok, checked. You can pass an array as argument. But also, is your event class path correct? In PHP >= 5.5 you can do `$this->expectsEvents(App\Events\GroupMediaSaving::class);`, will let you know if the path is invalid. – alariva Mar 25 '16 at 01:13
  • Nope i have checked the paths, they are all proper for my events and map to the actual classes. – Joseph Crawford Mar 25 '16 at 13:33
  • Where are you triggering the fire event of `App\Events\GroupMediaSaving`, can you post that code ? – alariva Mar 25 '16 at 14:41
  • I have added that to the end of the question. – Joseph Crawford Mar 25 '16 at 15:21
  • As stated in the question, the event is being fired but when using the setUp method to create data objects created by Factory Models then the system starts to fail to see that the event was fired when it actually was fired. I have verified by putting debugging code in the event listener and it is being executed. – Joseph Crawford Mar 25 '16 at 15:22
  • So it seems to me that the event is fired in a static context, while you are doing `$this->expectsEvents` to evaluate a concrete instance. Is there any particular reason you are using `static` methods and attributes? – alariva Mar 25 '16 at 16:39
  • That is how events are handled in laravel 5.2, they expect Fully Qualified Class Names (FQCN). I am firing the event from the static saving method and am using the Event Facade to throw that CONCRETE event class, notice the new keyword? Everything worked fine until i used the setup method to generate factory models and store them in properties on the test class for use through multiple tests. – Joseph Crawford Mar 28 '16 at 13:26

2 Answers2

16

If you look at the source code for expectsEvents (inside the trait Illuminate/Foundation/Testing/Concerns/MocksApplicationServices), you will see that it calls the function withoutEvents, which mocks the application event dispatcher, suppressing and collecting all future events.

The problem for you is that the setUp function will have already been called at this point, so your events will not be caught and logged by the test, and will not show up when the assertion is evaluated.

In order to correctly see the events firing, you should make sure that you declare the assertion before the code which triggers the events.

beeglebug
  • 3,522
  • 1
  • 23
  • 39
3

Same thing happened to me, $this->expectsEvent() was not detecting that the event was being fired or prevent it from propagating to the event listeners..

Previously, I was firing my events using Event::fire(new Event()). I tried changing it to event(new Event()) and the test suddenly works correctly now, with it detecting that the event has been fired and silencing the event.

Mastergalen
  • 4,289
  • 3
  • 31
  • 35
  • Same thing happened to me. Using `Event::fire()` caused the test to fail but using `event()` passes – Jack Aug 03 '17 at 18:54
  • @Jack It's been some time since this post, but using `event()` also fails with a risky test: no assertions made. PHP 7.4.4, Lumen (7.2.1) (Laravel Components ^7.0) – s3c Sep 22 '22 at 13:59