Question

I'm grappling with mocking/Mockery for the first time and I'm unsure if the following test is actually touching my code, or is only testing the mock I've made? Also, I realize this code doesn't properly fit the repository pattern despite the fact it's name as such.. I'll work on that.

The class:

<?php namespace Acme\Cart\Repositories;

class EloquentCartRepository{
    protected $model_name = 'CartModel';
    protected $model;
    public function __construct($model = null)
    {
        $this->model = is_null($model) ? new $this->model_name : $model;
    }

    public function create_visitor_cart($session_id,$type = 'main'){
        return $this->create('visitor',$session_id,$type);
    }
    protected function create($user_type = null,$user_identifier = null,$type = 'main')
    {
        if(is_null($user_identifier)) throw new \Exception('Cannot create create cart, missing user identifier');
        if(is_null($user_type)) throw new \Exception('Cannot create create cart, missing user type');
        if($user_type == 'visitor')
        {
            $this->model->user_session_id = $user_identifier;
        }
        else
        {
            $this->model->user_id = $user_identifier;
        }
        $this->model->type = $type;
        $this->model->save();
        return $this->model;
    }
}

And my test:

/** @test */
public function create_visitor_cart_calls_internal()
{
    $model = m::mock('Models\CartModel');
    $model->shouldReceive('user_session_id')->with('sess123');
    $model->shouldReceive('type')->with('main');
    $model->shouldReceive('save')->andReturn($model);

    $repository = new EloquentCartRepository($model);
    $created_model = $repository->create_visitor_cart('sess123','main');
    $this->assertEquals('sess123',$created_model->user_session_id);
    $this->assertEquals('main',$created_model->type);
}

Is this a proper way to test my class? Or is this incorrect use of Mockery/mocking?

Was it helpful?

Solution

Instead of testing what is returned, you should test that it is saved. That means, that ->save() is run. The expectation you've set on ->save() is $model->shouldReceive('save')->andReturn($model);. That doesn't make sense, since the code doesn't use the return value of ->save().

In programming, you usually deal with 2 types of method: Commands and Queries. Queries can get some value, do some logic and return a value. Commands can get some values, communicates with an extern source (e.g. a database) and return nothing. Queries should be stubbed (that means, they should not do any expectations on how much it is called, but only on what it returns) and commands should be mocked (that means, they should only contain expectatations on how much (and if) it is called).

The ->save() method is a command: It communicates with the database. So it should be mocked. To mock the object, use the ->once() method of Mockery. It sets an expectation that it should be called one time:

/** @test */
public function create_visitor_cart_calls_internal()
{
    $model = m::mock('Models\CartModel');
    $model->shouldReceive('save')->once();

    $repository = new EloquentCartRepository($model);
    $created_model = $repository->create_visitor_cart('sess123','main');
    $this->assertEquals('sess123',$created_model->user_session_id);
    $this->assertEquals('main',$created_model->type);
}

Despite its name, Mockery is a stubbing framework by default. It does not validate that a method is called unless you explicitely specify an expectation like ->once()

For more information, see the docs: https://github.com/padraic/mockery-docs/blob/master/reference/expectations.rst

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top