9

コントローラー内の Eloquent モデルへの連鎖呼び出しを適切にモックしようとしています。私のコントローラーでは、依存性注入を使用してモデルにアクセスしているため、簡単にモックできますが、連鎖呼び出しをテストして正しく機能させる方法がわかりません。これはすべて、PHPUnit と Mockery を使用した Laravel 4.1 にあります。

コントローラ:

<?php

class TextbooksController extends BaseController
{
    protected $textbook;

    public function __construct(Textbook $textbook)
    {
        $this->textbook = $textbook;
    }

    public function index()
    {
        $textbooks = $this->textbook->remember(5)
            ->with('user')
            ->notSold()
            ->take(25)
            ->orderBy('created_at', 'desc')
            ->get();

        return View::make('textbooks.index', compact('textbooks'));
    }
}

コントローラーのテスト:

<?php

class TextbooksControllerText extends TestCase
{
    public function __construct()
    {
        $this->mock = Mockery::mock('Eloquent', 'Textbook');
    }

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

    public function testIndex()
    {
        // Here I want properly mock my chained call to the Textbook
        // model.

        $this->action('GET', 'TextbooksController@index');

        $this->assertResponseOk();
        $this->assertViewHas('textbooks');
    }
}

$this->action()テストで呼び出しの前にこのコードを配置することで、これを達成しようとしています。

$this->mock->shouldReceive('remember')->with(5)->once();
$this->mock->shouldReceive('with')->with('user')->once();
$this->mock->shouldReceive('notSold')->once();
$this->app->instance('Textbook', $this->mock);

ただし、これによりエラーが発生しますFatal error: Call to a member function with() on a non-object in /app/controllers/TextbooksController.php on line 28

また、それがうまくいくことを期待して、チェーンされた代替手段も試しました。

$this->mock->shouldReceive('remember')->with(5)->once()
    ->shouldReceive('with')->with('user')->once()
    ->shouldReceive('notSold')->once();
$this->app->instance('Textbook', $this->mock);

このチェーンされたメソッド呼び出しを Mockery でテストするために取るべき最善のアプローチは何ですか。

4

3 に答える 3

23

もともとはコメントでしたが、コードを読みやすくするために回答に移動しました!

私も@alexrussellの答えに傾いていますが、妥協点は次のとおりです。

$this->mock->shouldReceive('remember->with->notSold->take->orderBy->get')
    ->andRe‌​turn($this->collection);
于 2014-03-16T10:33:17.253 に答える
7

私は自分自身をテストするのはまったく初めてで、ほとんどの人の目にはこの答え全体が間違っているかもしれませんが、間違ったことをテストする人が蔓延していることは確かです. メソッドが行うすべてのことを正確にテストする場合は、テストしているのではなく、メソッドを 2 回書いているだけです。

コードはブラック ボックスのようなものと考える必要があります。テストを作成するときに内部で何が起こっているかを知っていると思い込まないでください。指定された入力でメソッドを呼び出し、出力を期待します。場合によっては、特定の他の効果が発生したことを確認する必要があり、そのときに shouldReceive が必要になります。しかし、これは、このコレクション チェーンのテストよりも高レベルです。このコードが行うことを実行するコードが完了したことをテストする必要がありますが、まさにコード自体が発生します。そのため、コレクション チェーンを何らかの形で他のメソッドに抽出する必要があり、そのメソッドが呼び出されることを単純にテストする必要があります。

(コードの目的ではなく) 実際に書かれたコードをテストすればするほど、より多くの問題が発生します。たとえば、コードを更新して同じことを別の方法で実行する必要がある場合 (おそらくそのチェーンの一部としてではremember(6)ないなど)、テストを更新して、呼び出してはならないときに呼び出されるようにする必要があります。まったくそれをテストします。remember(5)remember(6)

もちろん、このアドバイスはチェーンされたメソッドだけに当てはまるわけではありません。特定のメソッドをテストするときに、さまざまなオブジェクトでさまざまなメソッドが呼び出されることを確認するときはいつでもです。

「赤、緑、リファクタリング」という用語は嫌いですが、テスト方法が失敗する2つのポイントがあるため、ここで考慮する必要があります。

  • 赤/緑: 失敗するテストを最初に作成するときは、コードにこれらすべてのshouldReceives を含めないでください (意味がある場合は 1 つまたは 2 つ、上記を参照)。コードを書いています。そして実際には、最初にコードを書き、次にコードに適合するテストを書いたことを示しており、これはテスト ファーストの TDD に反しています。
  • リファクタリング: 最初にコードを書き、次にコードに適合するテストを行ったと仮定します (または、コードが魔法のように機能したテストに何を書くべきかを正確に推測することができました)。それは悪いことですが、それは世界の終わりではないので、あなたがやったとしましょう. ここでリファクタリングする必要がありますが、テストを変更しないとできません。テストはコードと密接に結びついているため、リファクタリングを行うとテストが壊れます。これも、TDD の考え方に反します。

テスト ファーストの TDD に従わない場合でも、テストを中断することなくリファクタリングのステップを実行できる必要があることを少なくとも認識する必要があります。

とにかく、それは私のタペンスです。

于 2014-02-13T10:57:48.267 に答える
4

私はこのテクニックを発見しましたが、好きではありません。非常に冗長です。これを達成するには、よりクリーンでシンプルな方法が必要だと思います。

コンストラクターで:

$this->collection = Mockery::mock('Illuminate\Database\Eloquent\Collection')->shouldDeferMissing();

テストでは:

$this->mock->shouldReceive('remember')->with(5)->andReturn($this->mock);
$this->mock->shouldReceive('with')->with('user')->andReturn($this->mock);
$this->mock->shouldReceive('notSold')->andReturn($this->mock);
$this->mock->shouldReceive('take')->with(25)->andReturn($this->mock);
$this->mock->shouldReceive('orderBy')->with('created_at', 'DESC')->andReturn($this->mock);
$this->mock->shouldReceive('get')->andReturn($this->collection);
于 2014-02-13T01:59:07.380 に答える