I took another crack at this and found calling Event::swap($instance) (instead of $this->app->instance('events', $instance)) will correctly restore Event::fire() connector behaviour after the $this->withoutEventsFor() closure.
class TestCase extends Illuminate\Foundation\Testing\TestCase
{
/**
* @var \App\Models\User
*/
protected $user;
/**
* If the Event facade is referenced before this method (either in
* $this->withoutEventsFor() or elsewhere), we must also refresh the
* facade's resolved instance to reflect the app container mock.
*
* @return $this
*/
protected function withoutEvents()
{
return tap(parent::withoutEvents(), function () {
Event::swap($this->app['events']);
});
}
/**
* Temporarily disable event firing for a test setup closure function.
*
* @param callable $callback
*
* @return mixed Value returned by the callback.
*/
protected function withoutEventsFor(callable $callback)
{
$events = $this->app['events'];
$this->withoutEvents();
return tap($callback(), function () use ($events) {
Event::swap($events);
});
}
}
Above is a /tests project class my test suite always extends. You could also implement this as a trait although this method depends on TestCase@withoutEvents() and $this->app declared in Illuminate\Foundation\Testing\TestCase.