Макет Laravel с моделями Mockery Eloquent

Я разрабатываю приложение PHP (5.4.25) на платформе laravel (4.2). Я хочу протестировать свой UserController с помощью Mockery, поэтому я подобрал свой UserController следующим образом:

class UsersController extends \BaseController {
    protected $user;

    public function __construct(User $user) {
        $this->user = $user;
        $this->beforeFilter('csrf', array('on'=>'post'));
    }

    public function store() {
        $validator = Validator::make(Input::all(), User::$rules);

        if ( $validator->passes() ) {
            $this->user->username = Input::get('username');
            $this->user->password = Hash::make(Input::get('password'));
            $this->user->first_name = Input::get('first_name');
            $this->user->last_name = Input::get('last_name');
            $this->user->email = Input::get('email');
            $this->user->save();


            return true;
        } else {
            return false;
        }
    }

Я хочу имитировать модель Eloquent User, поэтому я разрабатываю свой UsersControllerTest так:

class UsersControllerTest extends TestCase {

    private $mock;

    public function __construct() {}

    public function setUp() {
        parent::setUp();

        $this->createApplication();                                                     
    }

    public function tearDown() {
        parent::tearDown();

        Mockery::close();
    }

    public function testStore() {
        $this->mock = Mockery::mock('Eloquent','User[save]');                                           
        $this->mock
            ->shouldReceive('save')
            ->once()
            ->andReturn('true');                                                        
        $this->app->instance('User', $this->mock);                                      

        $data['username'] = 'qwerety';
        $data['first_name'] = 'asd';
        $data['last_name'] = 'asd123';
        $data['email'] = '[email protected]';
        $data['password'] = 'password';
        $data['password_confirmation'] = 'password';

        $response = $this->call('POST', 'users', $data);

        var_dump($response->getContent());
    }

} 

Когда я запускаю свой тест, он возвращает мне эту ошибку:

Mockery\Exception\InvalidCountException : Method save() from Mockery_0_User should be called
 exactly 1 times but called 0 times.

Почему? Что случилось?

РЕДАКТИРОВАТЬ: Я обнаружил проблему. Если я не использую фиктивный объект, все работает нормально, и контроллер создает нового пользователя в БД, но когда я использую макет, метод Input:all() возвращает пустой массив.

-- Спасибо


person Matteo Codogno    schedule 11.09.2014    source источник


Ответы (3)


Как вы это тестируете, в конструктор контроллера передается экземпляр реальной модели Eloquent, а не макет. Если вы хотите протестировать фасады (как четко указано в документации), вам следует позвонить в shouldReceive() прямо на фасаде, чтобы его высмеяли.

Но поскольку вы переопределяете переменную $this->user в методе store() контроллера, она не будет вызываться, если вы не удалите жестко запрограммированный экземпляр и не используете введенный $user.

Изменить: я переиграл линию $this->app->instance('User', $this->mock);. Итак, проблема может быть связана с тем, что в методе store вы получаете экземпляр нового класса напрямую, а не через контейнер Laravel IoC. вместо $this->user = new User; в методе вашего магазина вы должны получить его через App::make('User');

person Antonio Frignani    schedule 11.09.2014
comment
Также, удалив новый экземпляр класса в методе хранилища, я столкнулся с той же ошибкой. - person Matteo Codogno; 12.09.2014

У меня была такая же проблема, когда я начал тестирование ... Дело в том, что в вашем методе хранилища пользовательского контроллера вы фактически сохраняете пользователя в базе данных и на основе своего кода, он может работать нормально, но вы удивлены, что это на самом деле не проходит тест. Теперь подумайте об этом так, вы издеваетесь над пользователем, и вы сказали своему тесту, что когда я вызываю модель User, пожалуйста, дайте мне фиктивную версию моего пользователя, как вы это сделали в строке кода ниже

$this->app->instance('User', $this->mock);

Теперь проблема в том, что вам нужно также вызвать макетную версию метода save () из макетной версии User следующим образом:

$this->mock->save();

Рассмотрим следующий пример:

public function testStore()
{
    $input = ['name', 'Canaan'];

    $this->mock
        ->shouldReceive('create')
        ->once()->with($input);

    $this->app->instance('User', $this->mock);
    $this->mock->create($input);

    $this->call('POST', 'users', $input);

}

Надеюсь, это вам поможет.

person Canaan Etai    schedule 13.09.2014
comment
Если я не использую макет объекта, все работает нормально, и контроллер создает нового пользователя в БД, но когда я использую макет, метод Input:all() возвращает пустой массив. - person Matteo Codogno; 13.09.2014
comment
Разве не побеждает логику повторный вызов $this->mock->create($input) в тестовом классе. Мне кажется, что это обман теста и заставляет его пройти тест контроллера, что означает, что тест пройдет независимо от того, был ли в контроллере (тот, который тестируется) был сделан вызов $this->user->save(); - person Moses Ndeda; 08.04.2017

Проблема была в $this->createApplication();.

Я прокомментировал эту строку, и Input::all() отлично работает со всеми входными параметрами!

person Matteo Codogno    schedule 13.09.2014