Mārtiņš Briedis
Mārtiņš Briedis

Reputation: 17772

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?

Upvotes: 8

Views: 2647

Answers (2)

paulus
paulus

Reputation: 655

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.

Upvotes: 4

Rutger
Rutger

Reputation: 21

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.

Upvotes: 1

Related Questions