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