Mocking Laravel Eloquent models - how to set a public property with Mockery

21,055

Solution 1

If you want getting this property with this value, just use it:

$mock->shouldReceive('getAttribute')
    ->with('role')
    ->andReturn(2);

If you call $user->role you will get - 2 ($user - its User mock class)

Solution 2

This answer is a bit late but hopefully it will help someone. You can currently set a static property on mocked Eloquent objects by using the 'alias' keyword:

$mocked_model = Mockery::mock('alias:Namespace\For\Model');
$mocked_model->foo = 'bar';
$this->assertEquals('bar', $mocked_model->foo);

This is also helpful for mocking external vendor classes like some of the Stripe objects.

Read about 'alias' and 'overload' keywords: http://docs.mockery.io/en/latest/reference/startup_methods.html

Solution 3

To answer your question, you could also try something like this:

$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
$mock->shouldReceive('setAttribute')->passthru();
$mock->roles = 2; 

$mock->shouldReceive('getAttribute')->passthru();
$this->assertEquals(2, $mock->roles);

Or, as suggested by seblaze, use a partial mock:

$mock = Mockery::mock('User[hasRole]');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2; 
$this->assertEquals(2, $mock->roles);

But, from your code snippet, if you're writing unit tests, you should really only make one assertion per each test:

function test_someFunctionWhichCallsHasRole_CallsHasRole() {
    $mock = Mockery::mock('User');
    $mock->shouldReceive('hasRole')->once();

    $mock->someFunctionWhichCallsHasRole();
}

function test_someFunctionWhichCallsHasRole_hasRoleReturnsTrue_ReturnsTrue() {
    $mock = Mockery::mock('User');
    $mock->shouldReceive('hasRole')->once()->andReturn(true);

    $result = $mock->someFunctionWhichCallsHasRole();

    $this->assertTrue($result);
}        

Solution 4

Spy is your friend on this:

$mock = Mockery::spy('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertTrue(someTest($mock));
Share:
21,055
mtmacdonald
Author by

mtmacdonald

Senior Software engineer at Oliasoft. I build web apps (full stack developer).

Updated on October 03, 2020

Comments

  • mtmacdonald
    mtmacdonald over 3 years

    I want to use a mock object (Mockery) in my PHPUnit test. The mock object needs to have both some public methods and some public properties set. The class is a Laravel Eloquent model. I tried this:

    $mock = Mockery::mock('User');
    $mock->shouldReceive('hasRole')->once()->andReturn(true); //works fine
    $mock->roles = 2; //how to do this? currently returns an error
    $this->assertTrue(someTest($mock));
    

    ... but setting the public property returns this error:

    BadMethodCallException: Method Mockery_0_User::setAttribute() does not exist on this mock object

    This error is not returned when mocking a simple class, but is returned when I try to mock an Eloquent model. What am I doing wrong?

  • mtmacdonald
    mtmacdonald about 10 years
    Yes I've read this page but no example is provided. When I've tried set() I get BadMethodCallException: Method Mockery_0_User::set() does not exist on this mock object. What I'm really looking for is an example.
  • mtmacdonald
    mtmacdonald about 10 years
    BadMethodCallException: Method Mockery_0_User::setAttribute() does not exist on this mock object.
  • Oldek
    Oldek about 10 years
    Updated my response, it will give you code complete in your IDE as well when you use user object
  • mtmacdonald
    mtmacdonald about 10 years
    thanks but isn't this just changing the implementation of the User class to add getters and setters? While that might be a workaround I don't see how that answers the original question.
  • Oldek
    Oldek about 10 years
    knowing that this is a persistence object from Lavarel is probably good to know :> Now according to Mockery it is fine to inject public variables, but it does not support magic functions, I do not know enough about Lavarel to say what the issue is here. It does look like ->roles gets transformed to ->setAttribute('roles', $value); but I could be wrong.
  • AndrewMcLagan
    AndrewMcLagan almost 7 years
    No you cant if your object expects an instance of that model class.
  • Yevgeniy Afanasyev
    Yevgeniy Afanasyev over 6 years
    I'm getting Undefined property: Mockery\CompositeExpectation::$role
  • Yevgeniy Afanasyev
    Yevgeniy Afanasyev over 6 years
    I'm getting error "Could not load mock App\Models\User, class already exists".
  • DuelistPlayer
    DuelistPlayer over 4 years
    When doing an alias mock due to a public static method, I was getting similar ErrorException: Undefined property: App\User::$role. In this case, a solution for me was to set the prop directly like $mock->role = 2. (Php 7.1, PHPUnit 6.5.14, Mockery 1.2.2, laravel test case Illuminate\Foundation\Testing\TestCase)