phpunit not finding model method (Laravel / Mockery)

I'm getting started with unit testing in Laravel 4, and I'm stuck testing a custom method in a model I've added to the standard User model.

use IlluminateAuthUserInterface;
use IlluminateAuthRemindersRemindableInterface;

class User extends BaseModel implements UserInterface, RemindableInterface {

    /**
     * Logs to the database and streams an update
     * Uses logIt and streamIt on Base model
     * @param  String $action   The action being performed
     * @return void         
     */    
    private function logAndStream($action) 
    {
        $this->logIt('info', $action.'d user '.$this->username);
        $this->streamIt($action.'d user '.$this->username);           
    } 

This class extends the BaseModel which in turn extends Eloquent, and has the defines the logIt and StreamIt methods like so:

class BaseModel extends Eloquent {

/**
 * Log an action to log file
 * @return void
 */
protected function logIt($level, $msg) {
    ...
} 

/**
 * Log an action to activity stream
 * @return void
 */
protected function streamIt($msg, $client = null, $project = null) {
    ... 
}   

All of this code works fine when I'm manually testing things. But now I want to create a unit test to automate it.

class UserTest extends TestCase {

public function testLogAndStream() 
{
    $base = Mockery::mock('BaseModel')->shouldAllowMockingProtectedMethods();
    $base->shouldReceive('logIt')
               ->with('info', 'Created user Tester')
               ->once();

    $user = new User;
    $user->username = 'Tester';
    $user->logAndStream('Create');
}

When I try running this, I get a failure complaining about not finding logAndStream.

1) UserTest::testLogAndStream
BadMethodCallException: Call to undefined method IlluminateDatabaseQueryBuilder::logAndStream()

What am I missing?


You have two problems here:

First , your User model contains a logAndStream method, but it's private. This means that doing this:

$user->logAndStream('Create');

is not possible because only public methods are accessible in this way.

Second , in your test, you are mocking an instance of BaseModel . This has nothing to do with the instance of User that you instantiate a couple of lines later. User extends BaseModel - you can still have instances of User and BaseModel that are not related to one another. Here's an analogy:

class Database {
}

class DatabaseWithExtraFeatures extends Database {
}

The first one ( Database ) is just a plain old database access class and works just fine for basic stuff. Then someone comes along and realizes that Database doesn't provide some extra feature, so they build upon it by extending it. A developer can use either, or even both, in the same application.

$db = new Database;

$dbExtra = new DatabaseWithExtraFeatures;

// do something with $db
$result1 = $db->query();

// do something with $dbExtra
$result2 = $dbExtra->extraSpecialQuery();

What you've done in your test is analogous to this - you've mocked one instance of BaseModel and then instantiated a User class (which just happens to extend BaseModel ).

EDIT Here's a little more detail on how to mock the user model:

$user = Mockery::mock('User')->shouldAllowMockingProtectedMethods();

$user->shouldReceive('logIt')
           ->with('some arguments')
           ->once();

$user->shouldReceive('streamIt')
           ->with('some arguments')
           ->once();

// when you set a property on an Eloquent model, it actually calls the
// setAttribute method. So do this instead of $user->username = 'Tester'
//
$user->shouldReceive('setAttribute')
    ->with('username', 'Tester')
    ->once()

$user->logAndStream('Create');

// now assert something...

Because User inherits from BaseModel , the methods from BaseModel are actually methods on User and can be treated as if they were defined as part of User .


Disclaimer: This was extracted from the question.

Firstly, I should have been checking that logIt and streamIt where observed on the same, mocked User model and not the parent BaseModel.

Populating the mock user model with $user->username was not correct either. In the end, Kryten helped me realise that Eloquent internally calls getAttribute('username') for this anyway, so I can return the value directly as part of an assertion, which would then be fed into logIt and StreamIt. This works but feels a little clunky - if anybody can suggest a better way, I'd love to learn.

Here's the working test case which works irrespective of whether logAndStream is declared as either a public or protected:

public function testLogAndStream() 
{ 
$user = Mockery::mock('User');

$user->shouldReceive('getAttribute')
     ->with('username')
     ->atLeast()->once()
     ->andReturn('Tester');

$user->shouldReceive('logIt')
     ->once()
     ->with('info','Created user Tester');

$user->shouldReceive('streamIt')
     ->once()
     ->with('Created user Tester');

$user->logAndStream('Create');      
}
链接地址: http://www.djcxy.com/p/83192.html

上一篇: 未调用Laravel模型事件绑定

下一篇: phpunit找不到模型方法(Laravel / Mockery)