Издевательский макет не возвращает указанное значение

Я использую Mockery в своем проекте Laravel, чтобы издеваться над моделью User Eloquent и тестировать маршрут.

Вот как я тестирую маршрут /api/user/activate:

<?php

use Illuminate\Support\Facades\Session;

class ActivateTest extends TestCase
{
    private $userMock;

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

        $this->userMock = Mockery::mock('App\User');

        Session::start();
    }

    public function tearDown()
    {
        Mockery::close();
    }

    public function testActivate()
    {
        $this->userMock->shouldReceive('where->first')->once()->andReturn('test');
        $this->userMock->shouldReceive('activate')->once();

        $response = $this->call('POST', '/api/user/activate', [
            'activationToken' => '838jfjnvu83u3',
            '_token' => csrf_token()
        ]);

        // This will be displayed in the PHPunit output
        print_r($response->getContent());

        $this->assertResponseStatus(200);
    }
}

У меня проблема в том, что andReturn('test') не работает. Результат PHPunit:

F{"error":{"message":null,"statusCode":404}}

Time: 276 ms, Memory: 15.50Mb

There was 1 failure:

1) ActivateTest::testActivate
Failed asserting that 404 matches expected 200.

Это содержимое activate() в UserController:

public function activate(Request $request)
{
    $activation = $request->input();

    $user = $this->user->where('activationToken', $activation['activationToken'])->first();

    if(!$user) return $this->respondNotFound($user);

    try
    {
        $user->activate($activation['password']);
    }
    catch(ModelException $e)
    {
        return $this->respondInternalError($e->errorMessages());
    };

    return $this->respondCreated('Account activated.');
}

Проблема в том, что $user в контроллере равно null, потому что макет не возвращает test (в этом случае условие будет оцениваться как истинное, и я не получу ответ 404).

Изменить:

Я также пытался использовать насмешку PHPunit, но это не увенчалось успехом:

$this->userMock = $this->getMockBuilder('App\User')->setMethods(['where', 'first', 'activate'])->getMock();

$this->userMock->expects($this->once())->method('where')->willReturn($this->userMock);
$this->userMock->expects($this->once())->method('first')->willReturn('test');
$this->userMock->expects($this->once())->method('activate');

person siannone    schedule 15.04.2015    source источник
comment
Я еще не очень хорошо знаком с Mockery, но я думаю, что where->first невозможен, потому что это недопустимая функция. Функция/атрибут, вызываемый для объекта user, — это where, и она возвращает объект, который вы вызываете first() на...   -  person Sven van Zoelen    schedule 15.04.2015
comment
Что ж, глядя на этот вопрос, похоже, я могу это сделать .   -  person siannone    schedule 15.04.2015
comment
Я не вижу ..->shouldReceive('where->first') в заданном вопросе. Только в заглушке есть что-то об этом, но это расширение или что-то в этом роде.   -  person Sven van Zoelen    schedule 15.04.2015
comment
Вы можете сделать shouldReceive('select-›where-›runQuery-›fetch'), если вам не нужны аргументы. В любом случае, анализируя код, я заметил, что может быть причиной проблемы. Вероятно, проблема связана с тем, что функция first() на самом деле не является функцией красноречивой модели.   -  person siannone    schedule 15.04.2015
comment
Ах, спасибо, не знал, что цепочка возможна. Попробую это!   -  person Sven van Zoelen    schedule 16.04.2015


Ответы (2)


Недостаточно издеваться над объектом. Вам нужно, чтобы этот издевательский объект был внедрен в класс, который содержит эту функцию activate().

Вы также можете сделать это в своей функции setUp(). Попробуйте добавить это...

$this->app->instance('App/User', $this->userMock);

Это сообщит Laravel, когда вы хотите внедрить экземпляр App/User, вместо этого внедрить только что созданный фиктивный объект.

person user1669496    schedule 15.04.2015

Проблема была вызвана ->first(), так как этот метод не существует ни в классах Eloquent, ни в классах User.

Чтобы решить эту проблему, я создал новый UserRepository и внедрил его как зависимость в конструкторе контроллера.

class UserRepository implements UserRepositoryInterface
{
    /**
     * @var User
     */
    protected $user;

    /**
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * @param $activationToken
     * @return mixed
     */
    public function whereActivationToken($activationToken)
    {
        return $this->user->where('activationToken', $activationToken)->first();
    }
}

Впрыск в UserController:

public function __construct(UserRepository $userRepository)
{
    $this->userRepository = $userRepository;
}

А вот так сейчас выглядит тестовый класс PostActivateTest:

use Illuminate\Support\Facades\Session;

class PostActivateTest extends TestCase
{
    private $user;
    private $userRepositoryMock;

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

        $this->user               = Mockery::mock('App\User');
        $this->userRepositoryMock = Mockery::mock('Repository\Database\UserRepository');
        $this->app->instance('App\User', $this->user);
        $this->app->instance('Bloom\BloomCRM\Repository\Database\UserRepository', $this->userRepositoryMock);

        Session::start();
    }

    public function tearDown()
    {
        Mockery::close();
    }

    public function testActivate()
    {
        $this->userRepositoryMock->shouldReceive('whereActivationToken')->once()->andReturn($this->user);
        $this->user->shouldReceive('activate')->once();

        $this->call('POST', '/api/user/activate', [
            'activationToken' => '838jfjnvu83u3',
            'password' => 'test',
            '_token' => csrf_token()
        ]);

        $this->assertResponseStatus(201);
    }
}
person siannone    schedule 16.04.2015