-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eloquent model events are not triggered when testing #1181
Comments
Upon further investigation, the behaviour gets weirder. It seems like the model event only executes the very first time the action occurs within the test suite. To explain my particular scenario: I have an Image model, and an associated ImageService which handles |
Paste test. On May 5, 2013, at 2:58 AM, Pascal Zajac notifications@github.com wrote:
|
Ok, this is the most succinct example I can produce: public function testFolderCreationAndDeletion()
{
// Create a new image.
$image = Image::create(array());
// Make sure a new folder was created.
$path = ImageService::getPath($image);
$this->assertTrue(is_dir($path)); // Passes.
// Cleanup.
$image->delete();
$this->assertFalse(is_dir($path));
}
public function testAgain()
{
// Create a new image.
$image = Image::create(array());
// Make sure a new folder was created.
$path = ImageService::getPath($image);
$this->assertTrue(is_dir($path)); // Fails
// Cleanup.
$image->delete();
$this->assertFalse(is_dir($path));
} The exact same test, if run again as a separate test, will fail where noted. |
Can you just paste the entire test class that fails? On May 5, 2013, at 6:31 PM, Pascal Zajac notifications@github.com wrote:
|
use Instinct\Image;
use Instinct\User;
class ImageServiceTest extends TestCase {
public function testFolderCreationAndDeletion()
{
// Create a new image.
$image = Image::create(array());
ImageService::created($image);
// Make sure a new folder was created.
$path = ImageService::getPath($image);
$this->assertTrue(is_dir($path));
// Cleanup.
$image->delete();
ImageService::deleted($image);
$this->assertFalse(is_dir($path));
}
public function testEntityCleanup()
{
// Create a new image.
$image = Image::create(array());
ImageService::created($image);
// Create a new user.
$user = User::create(array(
'image_id' => $image->id
));
// Now delete the image.
$image->delete();
ImageService::deleted($image);
// Reload the user and make sure the image was removed from their profile.
$user = User::find($user->id);
$this->assertEquals(0, $user->image_id);
// Cleanup.
$user->delete();
}
} That's the whole class, as it stands, including my manual invocations of ImageService methods (which should actually be triggered by event bindings, and are when run outside of a testing environment). |
I've done some further investigation on this. It seems that the Dispatcher class is re-instantiated between each test run. Some events, like |
Honestly I don't get your test. You're passing model instances into the events methods. What is that even supposed to do? |
Ok, I think the class definition for the namespace Instinct;
class Image extends \Eloquent {
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'images';
/**
* The fields that can be changed via mass assignment.
* @var array
*/
protected $fillable = array(
'extension',
'title',
'description'
);
}
// Register event handlers.
Image::created('ImageService@created');
Image::deleted('ImageService@deleted'); As you can see, in the model's class file I'm defining two event handlers. When an Now comes my assumption: since I create the event handlers in the model class file, they are not being reloaded between tests because composer does a So it seems like the problem isn't with Laravel's event system but rather with where I'm defining my event bindings. Doing so in the model's class file seemed logical so they were clearly grouped together. Is there somewhere else I should put these sort of bindings? |
I read back over the documentation for Eloquent events and saw the static Is there a reason for resetting the event dispatcher? |
I would move your event bindings into a separate method like Image::registerEvents(); and then you can call that from the setUp method. |
That creates a different set of problems though - the I appreciate that we've travelled far from the original assertions of this ticket, so I'm happy to open a new one, but the underlying issues remain unresolved - either the |
Could also use Model::flushModelEvents to clear them out. On May 17, 2013, at 11:20 PM, Pascal Zajac notifications@github.com wrote:
|
Ah ok, that method (which is actually |
So is the solution for unit tests to Create Model::registerEvents(); Call that method in the boot method. Then in unit tests, in the setup method call And loop through the models to reset the events? |
@markalanevans Laravel already looks for a static public function setUp()
{
parent::setUp();
$this->resetEvents();
}
private function resetEvents()
{
// Define the models that have event listeners.
$models = array('Calendar', 'Event', ...);
// Reset their event listeners.
foreach ($models as $model) {
// Flush any existing listeners.
call_user_func(array($model, 'flushEventListeners'));
// Reregister them.
call_user_func(array($model, 'boot'));
}
} |
This is a hack or a work-around, not a solution to a problem that still remains in the framework (as far as I know) |
I've face this similar problem recently (see #4036), and I've investigated quite a bit. Sadly, most of the problem is related to static/non-static behavior (the A potential solution (which is far from the best) would be to reset the booted models when the dispatcher used is changed in public static function setEventDispatcher(Dispatcher $dispatcher)
{
static::$dispatcher = $dispatcher;
static::$booted = array();
} |
I agree with @buzzware that this is a workaround and not a fix for the issue. Events being run once then cleared for subsequent tests creates an inconsistent testing environment. Maintaining a list of models which contain observers and reattaching their events isn't a fix - but a workaround. Maybe models with events can be detected somehow and this functionality can be pushed down into a core TestCase class? |
We just ran into this exact issue setting up our tests after migrating from CodeIgniter. The above-mentioned method works but it is far from ideal. |
Same story here. Bummer. |
The solution to this is to register your model events in app/start/global.php, not the model's boot() method as recommended in the documentation, and especially not in the same file that the model class is defined! That's mixing procedural code with non-procedural and is just a huge no-no. |
Can you elaborate @anlutro? I would much prefer to define these event bindings outside the model class, but when I moved them to |
Same problem here. Any tips on this? |
@PascalZajac: Your solution didn't help as well. It recreates listeners, but still the event isn't triggered on second call from unit tests. I am trying to use the creating event. |
@harshilmathur the hack I outlined above last year still works for me in my test suite. Feel free to paste the code you're using in a gist and I'll comment on it. |
@PascalZajac FYI I have event listeners in the global file and my tests run fine without any hacks. |
@crynobone : @anlutro's comment has other sets of issues. The method doesn't work if you bind the event to the parent class and expect it to work for child class's event. The boot method works for that. |
I agree with @harshilmathur, model event binding inheritance is a very great feature and it needs to declare bindings in the And if mixing procedural with non-procedural code is a problem for somebody here, we have to remove PHP right now ! There is a lot of code in models that is close to configuration code ( |
+1, Will be work Model Events out of the box? Thanks. |
@harshilmathur I found your comment when searching for a way around that exact issue today. I ended up doing the following: Event::listen('eloquent.saving: *', function($model) {
if ($model instance of \My\Base\Model) {
// event code here
}
}); This allowed all of my models to inherit the event handling code without binding to any Eloquent models in 3rd party packages. Did you happen to come up with a better way? |
@travis-rei I ended up overriding the save function of the eloquent model.php in my eloquent model class to do what I wanted and then call the parent's save function. This solved all my issues because it can now be unit tested without issues as well as inherited saving me from writing same function in multiple child classes. |
Throwing some support in for a solution here. When using third party packages model events have to be registered in the |
Problem being? |
The discussion above seemed to indicate that this problem could be resolved if the model events were registered in |
Ah sorry, let me clarify. In a package, the equivalent of |
Ah, gotcha. This still wouldn't work in my instance (referring to watson/validating where it's a trait that registers an observer when the trait is booted on a model. |
I ran in the same issue today and seem to have fixed it by adding |
Very frustrating. Have just spent quite some time debugging this and then stumbled across this issue. I'm using @dwightwatson great watson/validating package, and it seems really hacky in my tests to flush and reboot before each. And if it is the road we should be using, then I think this should be mentioned somewhere in the unit testing docs. |
oh no, I've struggled with this problem for more than 3 hours and then I just now find now there are a lot of related issues in here. Can't believe it already exists for so long and Laravel doesn't want solve it at all?! |
@taylorotwell any new thoughts on this? Do you consider this not to be an issue with Laravel, and we should just use the fixes above? If not, maybe re-open the issue so that other people can find it more easily? |
This is super crazy! Here is my hacky workaround:
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 As an introduction: I'm using the models
Some test failed failed because the boot() method on my Since you can't "unload" a class in PHP, I decided to add an First, I created the following
Then you can simply add this
And finally, tell the
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. |
After spending some hours, I discovered that it is required to redefine model events after each single test with phpunit. [Here](laravel/framework#1181) is a description of the issue. By putting the events definition to a separate function we allow writing tests for Node instances.
There are a number of issues related to Laravel not firing model events when using PHPUnit laravel/framework#1181 laravel/framework#4975 This package seems to help with that https://github.com/orchestral/testbench
There are a number of issues related to Laravel not firing model events when using PHPUnit laravel/framework#1181 laravel/framework#4975 This package seems to help with that https://github.com/orchestral/testbench
There are a number of issues related to Laravel not firing model events when using PHPUnit laravel/framework#1181 laravel/framework#4975 This package seems to help with that https://github.com/orchestral/testbench
There are a number of issues related to Laravel not firing model events when using PHPUnit laravel/framework#1181 laravel/framework#4975 This package seems to help with that https://github.com/orchestral/testbench
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?The text was updated successfully, but these errors were encountered: