Closed PascalZajac closed 11 years ago
This is super crazy! Here is my hacky workaround:
trait ModelEventOverride {
public $events = [ 'saving' => 'beforeSave', 'saved' => 'afterSaved',
'creating' => 'beforeCreate', 'created' => 'afterCreated',
'updating' => 'beforeUpdate', 'updated' => 'afterUpdated',
'deleting' => 'beforeDelete', 'deleted' => 'afterDeleted',
'validating' => 'beforeValidate','validated' => 'afterValidated'
];
protected function fireModelEvent($event, $halt = true)
{
$_event = $event;
if ( isset(static::$dispatcher) ) {
$event = "eloquent.{$event}: ".get_class($this);
if ( !empty(static::$dispatcher->getListeners($event)) )
{
$method = $halt ? 'until' : 'fire';
return static::$dispatcher->$method($event, $this);
}
}
$event = $_event;
if ( ! isset($this->events[$event])) return true;
$method = $this->events[$event];
if(method_exists($this, $method))
{
return call_user_func(array($this, $method),$this);
}
return true;
}
}
lol
Just bumped into this one myself in Codeception's acceptance tests. Spent whole morning debugging just to discover that events aren't fired while in test......
@igorpan yep, I can understand how you feel. been there...
I don't know why, if it can't be fixed, adding a note on the doc is okay too. This could save us some time.
I've been using Laravel for three months now and have been very impressed with it so far, but running into this issue after writing only two small testcases for a small Model
was an unpleasant (and unexpected) surprise. It took me a couple of hours to find out that it was not my own class, but the test framework itself causing some of my tests to fail. And to pass when ran individually... I came up with the following "solution" which I didn't see in this thread, so I thought I'd share it with you:
As an introduction: I'm using the models boot()
method to implement record-level authorization: users may only update a model (in this case a Boat
) if they own it. For example:
class Boat extends Eloquent {
protected static function boot()
{
parent::boot();
static::updating(
function ($boat) {
if (Authority::cannot('update', 'Boat', $boat)) {
return false;
}
}
);
}
Some test failed failed because the boot() method on my Model
was only called once. The model events like 'update' were only registered with the event dispatcher of the first test, but not with the (new) event dispatchers of following tests because the Model
class was already "$booted
".
Since you can't "unload" a class in PHP, I decided to add an unboot()
method to my model. By calling it from TestCase::tearDown()
I tell the model it its no longer "$booted
". It will then boot()
again during the next test (whenever it instantiates a model). In short:
First, I created the following trait
to implement two methods, unbootIfBooted
and unboot()
. Maybe a bit of overhead, but basically the counterparts of bootIfNotBooted
and boot()
from the Model
. Note that unbootIfBooted()
was made public static
to be able to call it from a TestCase
.
trait UnbootTrait {
public static function unbootIfBooted()
{
$class = get_called_class();
if (isset(static::$booted[$class])) {
static::$booted[$class] = null;
// fireModelEvent('unbooting', false);
static::unboot();
// fireModelEvent('unbooted', false);
}
}
protected static function unboot()
{
}
}
Then you can simply add this trait
to the models you want to test, without the need to change its inheritance:
class Boat extends Eloquent {
use UnbootTrait;
And finally, tell the TestCase->teardown()
method to "unboot" the models it used (usually only the model that's being tested):
class BoatTest extends TestCase {
protected function tearDown()
{
parent::tearDown();
Boat::unbootIfBooted();
}
public function testCanUpdateMyOwnBoat() {}
public function testCannotUpdateSomeoneElsesBoat() {}
It's just a quick fix, but it works. Comments and suggestions are welcome. I hope it'll be a contribution to a definitive solution to this problem.
Interesting solution, thanks) Although, I prefer not to think about the need unbooting models when writing Unit tests. Therefore, I prefer the solution based on EloquentEventsMechanic class. Still, I'm looking forward to that this "feature" will fixed and everything will work out of the box)
The various Eloquent model events ending with
ing
are not being triggered during tests.I discovered this a while ago and assumed it was intended functionality (forcing you to test your event handlers separately, much as route filters are not run in test mode).
However today I discovered that the post-action event handler
created
is triggered when run in test mode. Is there a reason for the discrepancy?