phpunit не находит метод модели (Laravel/Mockery)

Я начинаю модульное тестирование в 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()

Что мне не хватает?


person Fixspec    schedule 25.06.2014    source источник


Ответы (2)


Здесь у вас две проблемы:

Во-первых, ваша модель 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 (который просто расширяет 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, методы из BaseModel на самом деле являются методами User и могут рассматриваться так, как если бы они были определены как часть User.

person Kryten    schedule 25.06.2014
comment
Отлично! Спасибо большое. Изменение logAndStream на public позволяет тесту получить к нему доступ, как и ожидалось. Как бы вы предложили проверить, что logIt и streamIt вызываются для одного и того же объекта User (учитывая, что sholdReceive недоступен для моделей)? - person Fixspec; 26.06.2014
comment
У меня никогда не было проблем с использованием shouldReceive на издевательских моделях. Я отредактирую ответ, чтобы добавить краткий фрагмент. - person Kryten; 27.06.2014
comment
Большое спасибо. Сейчас я запутался и опубликую окончательный код. Кстати, моя ссылка на shouldReceive была на (реальных) моделях Eloquent, а не на моделях Mockery. Я не думаю, что смогу что-то вытащить из реальной БД и протестировать shouldReceive. Если есть способ сделать это, то мне было бы интересно узнать. - person Fixspec; 28.06.2014
comment
Ааа, теперь я понимаю. Я думаю, вы правы - я не думаю, что это возможно. Хотя вы можете сделать что-то подобное, если используете репозиторий. - person Kryten; 28.06.2014

Отказ от ответственности: это было извлечено из вопроса.

Во-первых, я должен был проверить, что 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');      
}
person Community    schedule 01.07.2014