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

#laravel #mockery

#laravel #издевательство

Вопрос:

Я начинаю с модульного тестирования в Laravel 4, и я застрял в тестировании пользовательского метода в модели, которую я добавил к стандартной модели пользователя.

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

Этот класс расширяет базовую модель, которая, в свою очередь, расширяет 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 IlluminateDatabaseQueryBuilder::logAndStream()
  

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

Ответ №1:

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

Во-первых, ваша User модель содержит logAndStream метод, но он частный. Это означает, что выполнение этого:

 $user->logAndStream('Create');
  

невозможно, потому что таким образом доступны только общедоступные методы.

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

Комментарии:

1. Отлично — большое вам спасибо. Изменение logAndStream на общедоступный позволяет тесту получить к нему доступ, как и ожидалось. Как бы вы предложили проверить, что logIt и streamIt вызываются для одного и того же пользовательского объекта (учитывая, что sholdReceive недоступен для моделей)?

2. У меня никогда не было проблем с использованием shouldReceive на издевательских моделях. Я отредактирую ответ, чтобы добавить краткий фрагмент.

3. Большое спасибо. Теперь я запутался и опубликую окончательный код. Кстати, моя ссылка на shouldReceive была на (реальных) моделях Eloquent, в отличие от моделей Mockery. Я не думаю, что смогу извлечь что-то из реальной базы данных, и тест должен быть получен. Если есть способ сделать это, мне было бы интересно узнать.

4. Ах, теперь я понимаю. Я думаю, вы правы — я не думаю, что это возможно. Хотя вы могли бы сделать что-то подобное, если используете репозиторий .

Ответ №2:

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

Во-первых, я должен был проверять, что logIt и streamIt наблюдаются в одной и той же, издевательской модели пользователя, а не в родительской базовой модели.

Заполнение макетной пользовательской модели также $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');      
}