简体   繁体   English

在Mockery中测试链式方法调用

[英]Testing chained method call in Mockery

I'm trying to properly mock a chained call to an Eloquent model in a controller. 我正在尝试在控制器中正确模拟对Eloquent模型的链式调用。 In my controller I'm using dependancy injection to access the model so that it should be easy to mock, however I'm not sure how to test the chained calls and make it work right. 在我的控制器中,我使用依赖注入来访问模型,以便它易于模拟,但是我不确定如何测试链式调用并使其正常工作。 This is all in Laravel 4.1 using PHPUnit and Mockery. 这完全在使用PHPUnit和Mockery的Laravel 4.1中。

Controller: 控制器:

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

Controller test: 控制器测试:

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

I've been trying to achieve this by placing this code before the $this->action() call in the test. 我一直试图通过在测试中的$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);

However, this results in the error Fatal error: Call to a member function with() on a non-object in /app/controllers/TextbooksController.php on line 28 . 但是,这会导致错误Fatal error: Call to a member function with() on a non-object in /app/controllers/TextbooksController.php on line 28

I've also tried a chained alternative hoping it would do the trick. 我也尝试了一种链式替代方案,希望它可以做到这一点。

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

What is the best approach I should take to testing this chained method call with Mockery. 使用Mockery测试这个链式方法调用应该采取的最佳方法是什么。

Originally a comment but moved to answer to make the code legible! 最初的评论,但移动回答,使代码清晰可辨!

I lean toward @alexrussell's answer too, though a middle ground would be: 我也倾向于@ alexrussell的答案 ,尽管中间地带是:

$this->mock->shouldReceive('remember->with->notSold->take->orderBy->get')
    ->andRe‌​turn($this->collection);

I'm quite new to testing myself, and this whole answer may be wrong in most people's eyes, but I do see a prevalence of people testing the wrong thing. 我对自己的测试很陌生,在大多数人看来,这个答案可能都是错误的,但我确实看到人们普遍都在测试错误的东西。 If you test exactly everything a method does then you're not testing, but just writing a method twice. 如果你测试一个方法所做的一切,那么你就不会测试,而只是编写一个方法两次。

You should think of your code as something of a black box - don't presume to know what's going on inside when you write your tests. 你应该把你的代码想象成一个黑盒子 - 不要假设你在编写测试时知道里面发生了什么。 Call a method with a given input, expect an output. 使用给定输入调用方法,期望输出。 Sometimes you need to ensure that certain other effects have happened, and that's when the shouldReceive stuff comes in. But again it's more high level than this collection chain testing - you should be testing that the code to do what this code does is done, but exactly that the code itself happens. 有时你需要确保发生了某些其他的影响,那就是当shouldReceive的东西进来时。但是它再次比这个集合链测试更高级 - 你应该测试代码来完成这段代码所做的事情,但是正是代码本身发生了。 As such, the collection chain should be extracted to some other method somehow, and you should simply test that that method is called. 因此,应该以某种方式将集合链提取到其他方法,并且您应该只测试该方法被调用。

The more you're testing the actual written code (rather than the purpose of the code) the more problems you will have. 您测试实际编写的代码(而不是代码的目的)越多,您将遇到的问题就越多。 For example, if you need to update the code to do the same thing a different way (maybe remember(6) not remember(5) as part of that chain or something), you also have to update your test to ensure that remember(6) is now called, when you shouldn't be testing that at all. 例如,如果您需要更新代码以不同的方式执行相同的操作(可能remember(6)remember(5)作为该链或其他内容的一部分),您还必须更新您的测试以确保remember(6)现在被调用,当你根本不应该测试它。

This advice doesn't just go for chained methods of course, it's for any time you ensure that various objects have various methods called on them when testing a given method. 当然,这个建议并不仅仅适用于链式方法,只要您确保在测试给定方法时各种对象都有各种方法调用它们。

As much as I dislike the term 'red, green, refactor' you should consider it here as there are two points where your testing method is failing: 尽管我不喜欢“红色,绿色,重构”这个术语,但你应该在这里考虑它,因为你的测试方法有两点失败:

  • Red/Green: When you first write the failing test, your code shouldn't have all these shouldReceive s (maybe one or two if it makes sense to, see above) - if it does, then you're not writing a test but you're writing the code. 红色/绿色:当您第一次编写失败的测试时,您的代码不应该具有所有这些shouldReceive s(如果有意义,可能是一两个,见上文) - 如果有,那么您不是在编写测​​试但是你在写代码。 And really, it's an indication that you wrote the code first then the test to fit the code, which is against test-first TDD. 实际上,这表明您首先编写代码然后测试代码以适应代码,这是针对测试优先的TDD。
  • refactor: Assuming you have written the code first, then the test to fit the code (or hey somehow managed to guess exactly what shouldReceives to write in your test that the code just magically worked out). 重构:假设你已经编写了代码,然后进行了测试以适应代码(或者嘿,他们设法猜测你的测试中应该写入的内容应该是什么,而代码只是神奇地计算出来了)。 That's bad, but let's say you did it, as it's not the end of the world. 这很糟糕,但让我们说你做到了,因为它不是世界末日。 You now need to refactor, but you can't without changing your test. 你现在需要重构,但你不能不改变你的测试。 You test is so closely coupled to the code, that any refactoring will break the test. 您的测试与代码密切相关,任何重构都会破坏测试。 That is, again, against the idea of TDD. 这也是反对TDD的想法。

Even if you don't follow test-first TDD, you should at least realise that the refactor step should be doable without breaking your tests. 即使您不遵循测试优先TDD,您至少应该意识到重构步骤应该是可行的,而不会破坏您的测试。

Anyway, that's just my tuppence. 无论如何,那只是我的傻瓜。

I've discovered this technique, but I don't love it. 我发现了这种技术,但我不喜欢它。 It's very verbose. 它非常冗长。 I think there must be a cleaner/simpler method to achieving this. 我认为必须有一个更清洁/更简单的方法来实现这一目标。

In the constructor: 在构造函数中:

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

In the test: 在测试中:

$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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM