35

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?

halfer
  • 19,824
  • 17
  • 99
  • 186
mtmacdonald
  • 14,216
  • 19
  • 63
  • 99
  • Maybe a daft question - but depending on what you're testing do you even need a mock object? If you're testing (say) the hasRole() method, Why not use $mock = new User; $mock->roles = 2; and test that? For example - if I'm testing accessors/presenters etc, I'll just a proper object rather than mock setAttribute etc. – Apemantus Apr 07 '14 at 10:32
  • @Apemantus thanks. I'm testing a model that has relationships (a user has many roles). I'm not aware of a way to set the relationships without saving to a database. I'm trying to write a test that doesn't need to touch the database. – mtmacdonald Apr 07 '14 at 10:53
  • OK. I haven't tested our relationships, but it is possible to do $mock->shouldReceive('setAttribute')->with('roles)->andReturn(2)- but that's only going to return the integer. You could return a second mock of a Role model if you wanted I guess, like in http://stackoverflow.com/questions/20361364/mocking-models-with-a-relationship-in-laravel?rq=1 – Apemantus Apr 07 '14 at 16:02

9 Answers9

26

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)

Joyful
  • 813
  • 8
  • 15
  • 2
    I'm getting Undefined property: Mockery\CompositeExpectation::$role – Yevgeniy Afanasyev Sep 15 '17 at 04:42
  • When doing an [alias mock](http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#aliasing) 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`) – Daryn Dec 08 '19 at 11:46
9

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

Fancypants_MD
  • 665
  • 1
  • 8
  • 8
4

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);
}        
awei
  • 1,154
  • 10
  • 26
1

Tried this? It should cover you issue.

https://github.com/padraic/mockery/blob/master/docs/11-MOCKING-PUBLIC-PROPERTIES.md

I'd say implement these

protected $roles = array();

public function setRoles($roles)
{
    $this->roles = $roles;
}

public function addRole($role)
{
    $this->roles[] = $role;
}

Then you can test using:

$mock = Mockery::mock('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->addRole(2);
$this->assertTrue(someTest($mock));

This apse gives you the opportunity to promise a format when you do a getRoles() which would be array of Role object if you do SOLID OOP, or if you rather use array, then at least you know it's always an array you get.

Oldek
  • 2,679
  • 5
  • 23
  • 25
  • 3
    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 Apr 07 '14 at 10:06
  • **BadMethodCallException: Method Mockery_0_User::setAttribute() does not exist on this mock object**. – mtmacdonald Apr 07 '14 at 10:10
  • Updated my response, it will give you code complete in your IDE as well when you use user object – Oldek Apr 07 '14 at 10:14
  • 6
    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. – mtmacdonald Apr 07 '14 at 10:17
  • 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. – Oldek Apr 07 '14 at 10:29
1

Spy is your friend on this:

$mock = Mockery::spy('User');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2;
$this->assertTrue(someTest($mock));
Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
0

Did you tried to do partial mocks ? You can try something like ( NOT TESTED ) :

$mock = Mockery::mock('User[hasRole]');
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2; 
$this->assertTrue(someTest($mock));
Paul Bele
  • 1,514
  • 1
  • 11
  • 12
0

Partial Mock in Laravel 5.3

$mock = Mockery::mock(App\User::class)->makePartial();
$mock->shouldReceive('hasRole')->once()->andReturn(true);
$mock->roles = 2; 
$this->assertEquals(2, $mock->roles);
aCiD
  • 1,273
  • 1
  • 18
  • 28
0

You need to use a partial mock so the rest of the eloquent model will function as normal. You're just wanting to override a certain method basically.

$this->partialMock(User::class, function ($mock) {
    $mock->shouldReceive('roles')->once();
});
Garrick Crouch
  • 309
  • 4
  • 11
-3

you can use stdclass in this case.

$obj = new stdClass();
$obj->roles = 2;
$mock = Mockery::mock('User');
// return stdclass here.
$mock->shouldReceive('hasRole')->once()->andReturn($obj);
$mock->roles = 2;
$this->assertEquals(2, $mock->roles);
shivanshu patel
  • 782
  • 10
  • 19