Laravel boot model bullying issue

I use the boot model feature to log specific events for models using my trait. However, I ran into a problem trying to simulate models that use a trait. In particular, when an instance of a Mockery version is simulated, the boot code agrees that it must have the bootMyTrait method, but cannot find it when it tries to call it.

An example repository for below, with commands to play.

As an example, here is a feature:

namespace App; trait MyTrait { public static function bootMyTrait() { print("Booting MyTrait\n"); } } 

And the model used:

 namespace App; use Illuminate\Database\Eloquent\Model; class MyModel extends Model { use MyTrait; } 

Actuation of the model regularly works fine. This shows the desired result:

 $model = new MyModel(); 

However, an attempt to mock this model does not work. It:

 use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; class ExampleTest extends TestCase { /** * A basic functional test example. * * @return void */ public function testTraitBooting() { $model = $this->getMock('App\MyModel'); } } Fails. Adding some debugging to Eloquent: /** * Boot all of the bootable traits on the model. * * @return void */ protected static function bootTraits() { $class = static::class; foreach (class_uses_recursive($class) as $trait) { print("\nTesting that class: $class has method: " . $method = 'boot'.class_basename($trait) . " because of Trait: $trait\n"); if (method_exists($class, $method = 'boot'.class_basename($trait))) { print("Class: $class has method: $method \n"); try { forward_static_call([$class, $method]); } catch (\PHPUnit_Framework_MockObject_BadMethodCallException $e) { print("Class: $class failed calling $method\n"); throw $e; } } } } 

Gives us this error:

 PHPUnit 5.1.0 by Sebastian Bergmann and contributors. E 1 / 1 (100%) Testing that class: Mock_MyModel_9ee820db has method: bootMyTrait because of Trait: App\MyTrait Class: Mock_MyModel_9ee820db has method: bootMyTrait Class: Mock_MyModel_9ee820db failed calling bootMyTrait Time: 129 ms, Memory: 18.00Mb There was 1 error: 1) ExampleTest::testTraitBooting PHPUnit_Framework_MockObject_BadMethodCallException: mock-bootable-laravel-model-trait/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:326 mock-bootable-laravel-model-trait/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:309 mock-bootable-laravel-model-trait/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:296 mock-bootable-laravel-model-trait/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:277 mock-bootable-laravel-model-trait/tests/ExampleTest.php:16 

I also tried creating a layout in several ways. Using DatabaseSoftDeletingTraitTest as an example:

 use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; use Mockery as m; class ExampleTest extends TestCase { /** * A basic functional test example. * * @return void */ public function testTraitBooting() { $mock = m::mock('App\MyModel'); $mock->shouldReceive('bootMyTrait')->once(); } } 

But here bootMyTrait is never called:

 PHPUnit 5.1.0 by Sebastian Bergmann and contributors. E 1 / 1 (100%) Time: 149 ms, Memory: 19.25Mb There was 1 error: 1) ExampleTest::testTraitBooting Mockery\Exception\InvalidCountException: Method bootMyTrait() from Mockery_0_App_MyModel should be called exactly 1 times but called 0 times. mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery/CountValidator/Exact.php:37 mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery/Expectation.php:271 mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120 mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery/Container.php:297 mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery/Container.php:282 mock-bootable-laravel-model-trait/vendor/mockery/mockery/library/Mockery.php:142 mock-bootable-laravel-model-trait/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:122 

So, I can port the code that I do in the download method to ServiceProvider, but then I will need to register every model that uses this trait. This seems messy, and using the download method seems appropriate. Therefore, I think that I either fell into error or incorrectly ridiculed a model using a trait. I looked at getMockForTrait , but I also need a fictional instance for the Eloquent extension (some of the trait methods are called eloquent methods)

If someone sees something that I missed (or if I completely get close to it wrong), it is greatly appreciated

+6
source share
1 answer

After some tests, I believe that such a method will be enough to test it:

 $mock = m::mock('App\MyModel')->makePartial(); $mock->shouldReceive('bootMyTrait')->once(); $mock->__construct(); 

Explanation:

  • $mock = m::mock('App\MyModel')->makePartial();

    We create a mock, but make it partial because we want to use the default class constructor and other methods. In part, this means that all methods that we do not override will be used from the original App\MyModel class

  • $mock->shouldReceive('bootMyTrait')->once();

    This should be obvious - we want to check if the bootMyTrait method bootMyTrait exactly 1 time

  • $mock->__construct();

    Thus, we can start the default constructor of the class. When creating the layout, it seems that the constructor is not used, so we cannot check it in another way. We need to manually start the object constructor method if we want to make sure that the original class constructor is running.

+2
source

All Articles