Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yii2 ActiveRecord mocking with Mockery

I'm running phpunit with mockery (without DB/fixtures), but I have a trouble with mocking a model.

$customer = Mockery::mock(CustomerModel::class);
echo $customer->id;

Produces error:

BadMethodCallException: Method Mockery_1_models_Customer::hasAttribute() does not exist on this mock object

Then I tried:

$customer->shouldReceive('hasAttribute')->andReturn(true);

But again, I run in to:

Fatal error: Call to a member function getDb() on a non-object in ..\yiisoft\yii2\db\ActiveRecord.php on line 135

Any suggestions?

like image 627
Mārtiņš Briedis Avatar asked Jul 03 '15 11:07

Mārtiņš Briedis


2 Answers

I know you are not accepting fixture answers, but I could not figure it out either. The problem I was trying to solve actually was mocking some methods in model so to avoid creating underlying fixtures.

So I ended up using Proxy pattern implementation in Mockery

private $_product;
public function testMe() 
{
   // Here we use fixtured instance of Product model to build a Proxy
   $this->_product = \Mockery::mock($this->product('product1')); 
   // somehow model attributes are inaccessible via proxy, but we can set them directly as a Proxy property
   $this->_product->id = 1; 
   $this->_product->shouldReceive('getPrice')->andReturn(1000);
   // assertions below
   ...
}

In this example getPrice() method from Product model returns Product price from related tables. And we mock it here so that we won't have to populate db with all related models fixtures. Nevertheless Product itself is still a fixture.

Maybe not the best solution, but managed to save me some CPU time while keeping unit tests decoupled.

Docs here http://docs.mockery.io/en/latest/reference/partial_mocks.html

update:

I also made a small helper to solve attribute proxying problem

/**
 * @param \yii\base\Model $model
 * @return \Mockery\MockInterface
 */
private function setupMock($model)
{
    $mock = \Mockery::mock($model);
    foreach ($model->getAttributes() as $key => $value) {
        $mock->$key = $value;
    }
    return $mock;
}

This way all attributes and their corresponding values from original model become available in mock.

like image 95
paulus Avatar answered Nov 05 '22 14:11

paulus


Is Traditional Partial Mock or Passive Partial Mock what you are looking for?

$customer = Mockery::mock(CustomerModel::className())->makePartial();
echo $customer->id;

The sample above would of course not return anything since id is not set, but wont throw any errors.

like image 1
Rutger Avatar answered Nov 05 '22 15:11

Rutger