Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

glockops's avatar

Prevent event listeners when using model factories.

Hey all, I'm trying to rerun previously successfully PHPUnit tests after adding a bunch of Listeners and I'm running into a wall.

I have listeners bound to the eloquent.created: {$model} event - which means that when a factory is created, these listeners are triggered. The end result is I have listeners being triggered for factories that are in other factories.

I've tried using $this->withoutEvents(); but always get a BadMethodCallException: Method Mockery_0_Illuminate_Contracts_Events_Dispatcher::until() does not exists on this mock object

Any ideas?

PHPUnit Test

/**
     * @test
     */
    public function it_can_be_instantiated() {

        $this->withoutEvents();

        $asset = factory(Dice\Models\Asset::class)->create();
        $this->assertInstanceOf(Dice\Models\Asset::class, $asset);
    }

Factory

$factory->define(Dice\Models\Asset::class, function($faker) use ($factory) {
   return [
       'report_id'      => $factory->create(Dice\Models\Report::class)->id,
       'domain'         => 'origin',
       'url'            => $faker->url,
       'status_code'    => 200,
       'bytes'          => $faker->randomNumber(4),
       'mime_type'      => 'text/html',
   ];
});
0 likes
9 replies
glockops's avatar

I finally traced down what was causing this. I'm using dwightwatson/validating which has a Event::until() method. As such, using $this->withoutEvents() breaks.

Overriding the default withoutEvents() function to add until solves the problem. You can add this to your TestCase file or Tests that need to prevent events on watson validating models.

    /**
     * Mock the event dispatcher so all events are silenced.
     *
     * @return TestCase
     */
    protected function withoutEvents()
    {
        $mock = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');

        $mock->shouldReceive('fire', 'until');

        $this->app->instance('events', $mock);

        return $this;
    }
2 likes
glockops's avatar
glockops
OP
Best Answer
Level 1

I'm back. Funny story actually, this same problem has popped up in a more complex manner. My original fix doesn't work in actually detaching the model observers.

If you have an eloquent model that is attached to a listener or observer you'll notice that the observer is still triggered when a model factory is used. Even when using withoutEvents().

Here's how I'm getting around it.

/tests/TestCase.php

{
    /**
      * Create a model factory and forget observers so events do not trigger actions.
      */
    public function factoryWithoutObservers($class, $name = 'default') {
        $class::flushEventListeners();
        return factory($class, $name);
    }
}

When needing to use a model factory in your tests, use the $this->factoryWithoutObservers() method rather than the standard factory() method.

Hope this helps someone! (Maybe me again in a year).

9 likes
bobbyborisov's avatar

hey @glockops , i got stuck on the same issue, in the laravel doc is says to use this $this->expectsEvents(App\Events\ProductUpdated::class);

but it doesn't seem to work. it gives me

BadMethodCallException: Method Mockery_0_Illuminate_Contracts_Events_Dispatcher::until() does not exist on this mock object

I tried you technique but it doesn't work... by the way any idea why this until method doesn't exist?

When i try to use flushEventListener method i got

BadMethodCallException: Method Mockery_0_Illuminate_Contracts_Events_Dispatcher::forget() does not exist on this mock object

And even i did it this way Product::flushEventListeners(); $product = factory(Product::class)->states('inStock')->create( ['ebay_price'=>1.4,'supplier_price'=>1] );

it still triggers my listeners

spasman's avatar

In Unit tests you can call Model::unsetEventDispatcher(); in your setUp() method.

6 likes
alaa_elshekh's avatar

You can ignore event dispatcher

 protected function setUp(): void
    {
        $this->invoiceService = app()->make(InvoiceService::class);

        parent::setUp();
        Models/ModelName::unsetEventDispatcher();
    }
5 likes
sardar1592's avatar

This has worked for me:

$model = Model::withoutEvents(function () {
            return factory(Model::class)->create();
        });
5 likes
bobbyaxe74's avatar

@sardar1592 this used to work in previous projects but strangely does not work on a particular project still on laravel 7.x

Please or to participate in this conversation.