Alex Kyriakidis
Alex Kyriakidis

Reputation: 2881

How to prevent Model events from firing using phpunit?

I want to prevent Model events such as 'created'. 'updated' etc when testing my application using phpunit.

In the documentation of Laravel it says that you can prevent events from firing by using

$this->expectsEvents(App\Events\UserRegistered::class);

But in my situation I have no class to expect.

Even if I use $this->withoutEvents(); to prevent all events, eloquent events are fired.

How can I prevent eloquent events?

Upvotes: 16

Views: 11994

Answers (5)

Jeff Puckett
Jeff Puckett

Reputation: 40861

If you really just want to disable the model's observer, such as creating, created, updated, etc. then you can call the model's façade method unsetEventDispatcher.

For example, you can place it in the test class's setUp method. And you don't have to reset it in tearDown because the framework automatically does this in setUp for the next test.

protected function setUp(): void
{
    parent::setUp();
    App\MyModel::unsetEventDispatcher();
}

If you want this to be temporary, then look into withoutEventDispatcher.

Also you could getEventDispatcher and save that to a temporary variable then setEventDispatcher again after you've completed your business as is demonstrated in this answer: https://stackoverflow.com/a/51301753/4233593

Upvotes: 8

Hyder B.
Hyder B.

Reputation: 12206

Method 1:

You can prevent model events from firing by mocking the Observer. Here's how:

public function my_sexy_test()
    {
        $this->app->bind(MyModelObserver::class, function () {
            return $this->getMockBuilder(MyModelObserver::class)->disableOriginalConstructor()->getMock();
        });

        factory(MyModel::class)->states('pending')->create([
                'user_id' => 1,
                'publisher_revenue' => 10,
        ]);

 // Your test logic goes here
}

Method 2:

You can call the following in your setUp() method:

 MyModel::unsetEventDispatcher();

Both Tested and working in Laravel 5.6.

Upvotes: 5

crynobone
crynobone

Reputation: 1814

You should be able to use

$model = new App\Model($inputs);

$this->expectsEvents(['eloquent.creating: App\Model', 'eloquent.saved: App\Model']);

$model->save();

Upvotes: 1

Pawel Bieszczad
Pawel Bieszczad

Reputation: 13325

Looking into Laravel Api Model::flushEventListeners() should "Remove all of the event listeners for the model."

EDIT

You can write a custom method base on this one:

public static function flushEventListeners()
{
    if (! isset(static::$dispatcher)) {
        return;
    }
    $instance = new static;
    foreach ($instance->getObservableEvents() as $event) {
        static::$dispatcher->forget("eloquent.{$event}: ".get_called_class());
    }
}

Something like this maybe:

public static function flushEventListenersProvided($events)
{
    if (! isset(static::$dispatcher)) {
        return;
    }
    $instance = new static;
    foreach ($instance->getObservableEvents() as $event) {
        if(in_array($event, $events)){
          static::$dispatcher->forget("eloquent.{$event}: ".get_called_class());
        }
    }
}

and maybe add it as a trait to the models, or a base model that all your models will extend. This code is untested but should give you an idea of how it can be done.

Upvotes: 13

cecilozaur
cecilozaur

Reputation: 705

Check out the shouldReceive method of the Model class. Basically the Model class extends Mockery. Here's an example assuming you have a class Car:

Car::shouldReceive('save')->once();

This will not hit the database but will use a mock instead. You can find more info on Laravel testing here: http://laravel.com/docs/4.2/testing

Hope this helps.

Upvotes: 1

Related Questions