0

我开始在 Laravel 4 中进行单元测试,并且我一直在测试我添加到标准用户模型中的模型中的自定义方法。

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

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);           
    } 

此类扩展了 BaseModel,后者又扩展了 Eloquent,并定义了 logIt 和 StreamIt 方法,如下所示:

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) {
    ... 
}   

当我手动测试时,所有这些代码都可以正常工作。但现在我想创建一个单元测试来自动化它。

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');
}

当我尝试运行它时,我抱怨找不到 logAndStream 失败。

1) UserTest::testLogAndStream
BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::logAndStream()

我错过了什么?

4

2 回答 2

0

你在这里有两个问题:

首先,您的User模型包含一个logAndStream方法,但它是私有的。这意味着这样做:

$user->logAndStream('Create');

是不可能的,因为只有公共方法才能以这种方式访问​​。

其次,在您的测试中,您正在模拟BaseModel. User这与您稍后实例化几行的实例无关。User 扩展 BaseModel- 您仍然可以拥有彼此不相关的User和的实例。BaseModel这是一个类比:

class Database {
}

class DatabaseWithExtraFeatures extends Database {
}

第一个 ( Database) 只是一个普通的旧数据库访问类,适用于基本的东西。然后有人出现并意识到它Database没有提供一些额外的功能,所以他们通过扩展它来构建它。开发人员可以在同一个应用程序中使用其中之一,甚至两者都使用。

$db = new Database;

$dbExtra = new DatabaseWithExtraFeatures;

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

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

您在测试中所做的与此类似 - 您已经模拟了一个实例,BaseModel然后实例化了一个User类(恰好是 extend BaseModel)。

编辑这里有更多关于如何模拟用户模型的细节:

$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...

因为User继承自BaseModel,所以方法 fromBaseModel实际上是方法 onUser并且可以被视为定义为 的一部分User

于 2014-06-25T23:02:25.343 回答
0

免责声明:这是从问题中提取的。

首先,我应该检查 logIt 和 streamIt 在同一个模拟用户模型而不是父 BaseModel 上观察到的位置。

填充模拟用户模型$user->username也不正确。最后,Kryten 帮助我意识到 Eloquent 内部getAttribute('username')无论如何都会调用它,所以我可以直接将值作为断言的一部分返回,然后将其输入 logIt 和 StreamIt。这可行,但感觉有点笨拙 - 如果有人可以提出更好的方法,我很乐意学习。

这是工作测试用例,无论logAndStream是声明为公共还是受保护的:

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');      
}
于 2014-07-01T14:52:01.973 回答