Laravel & Mockery: модульное тестирование метода обновления без обращения к базе данных

Итак, я новичок в модульном тестировании, насмешках и laravel. Я пытаюсь выполнить модульное тестирование моего контроллера ресурсов, но я застрял в функции обновления. Не уверен, что я делаю что-то не так или просто думаю неправильно.

Вот мой контроллер:

class BooksController extends \BaseController {

    // Change template.
    protected $books;

    public function __construct(Book $books)
    {
        $this->books = $books;
    }

    /**
     * Store a newly created book in storage.
     *
     * @return Response
     */
    public function store()
    {
        $data       = Input::except(array('_token'));
        $validator  = Validator::make($data, Book::$rules);

        if($validator->fails())
        {
            return Redirect::route('books.create')
                ->withErrors($validator->errors())
                ->withInput();
        }

        $this->books->create($data);

        return Redirect::route('books.index');
    }

    /**
     * Update the specified book in storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $book       = $this->books->findOrFail($id);
        $data       = Input::except(array('_token', '_method'));
        $validator = Validator::make($data, Book::$rules);

        if($validator->fails())
        {
            // Change template.
            return Redirect::route('books.edit', $id)->withErrors($validator->errors())->withInput();
        }

        $book->update($data);

        return Redirect::route('books.show', $id);
    }
}

И вот мои тесты:

public function testStore()
{
    // Add title to Input to pass validation.
    Input::replace(array('title' => 'asd', 'content' => ''));

    // Use the mock object to avoid database hitting.
    $this->mock
        ->shouldReceive('create')
        ->once()
        ->andReturn('truthy');

    // Pass along input to the store function.
    $this->action('POST', 'books.store', null, Input::all());

    $this->assertRedirectedTo('books');
}

public function testUpdate()
{
    Input::replace(array('title' => 'Test', 'content' => 'new content'));

    $this->mock->shouldReceive('findOrFail')->once()->andReturn(new Book());
    $this->mock->shouldReceive('update')->once()->andReturn('truthy');

    $this->action('PUT', 'books.update', 1, Input::all());      

    $this->assertRedirectedTo('books/1');
}

Проблема в том, что когда я делаю это так, я получаю Mockery\Exception\InvalidCountException: Method update() from Mockery_0_Book should be called exactly 1 times but called 0 times. из-за $book->update($data) в моем контроллере. Если бы я изменил его на $this->books->update($data), он был бы правильно смоделирован, и база данных не была бы затронута, но он обновил бы все мои записи при использовании функции из внешнего интерфейса.

Думаю, я просто хочу знать, как издеваться над $book-object properly.

Я достаточно ясен? Дайте мне знать иначе. Спасибо!


person Mattias    schedule 17.09.2014    source источник
comment
Сначала я подумал, что ваш $this->book был repository, но при ближайшем рассмотрении это больше похоже на просто модель Eloquent. Это правильно?   -  person Jeff Lambert    schedule 17.09.2014
comment
Верный. Я думал об использовании репозиториев, но решил, что на данный момент это вызовет у меня слишком много путаницы.   -  person Mattias    schedule 17.09.2014
comment
Проблема в том, что это одна из основных причин, по которой люди используют репозитории: это значительно упрощает тестирование.   -  person Jeff Lambert    schedule 17.09.2014
comment
Но должен же быть способ издеваться над единственным объектом $book, верно? Я действительно вынужден использовать шаблон репозитория для модульного тестирования всего контроллера?   -  person Mattias    schedule 17.09.2014


Ответы (2)


Попробуйте смоделировать метод findOrFail, чтобы он не возвращал new Book, а вместо этого возвращал фиктивный объект, на котором есть метод обновления.

$mockBook = Mockery::mock('Book[update]');
$mockBook->shouldReceive('update')->once();
$this->mock->shouldReceive('findOrFail')->once()->andReturn($mockBook);
person Jeff Lambert    schedule 17.09.2014
comment
Вы, добрый сэр, спасатель. Это помогло мне, и это даже имеет смысл. Спасибо! - person Mattias; 17.09.2014
comment
спс, рад, что смог помочь :) - person Jeff Lambert; 17.09.2014

Если ваша база данных является управляемой зависимостью, и вы используете mock в своем тесте, это приводит к хрупким тестам. Не имитируйте управление зависимостями.

Управление зависимостями: зависимости, над которыми у вас есть полный контроль.

person Alberto Oliveira    schedule 09.07.2021