Laravel + Mockery InvalidCountException

Posted

技术标签:

【中文标题】Laravel + Mockery InvalidCountException【英文标题】: 【发布时间】:2019-04-21 00:10:42 【问题描述】:

我正在尝试模拟 a class 以防止它不得不调用 3rd 方 api。但是在设置模拟时,它似乎并没有影响控制器的动作。我确实尝试通过手动创建Request- 和OEmbedController-classes 的实例来替换$this->postJson()create()-method 被调用,但我收到来自 Mockery 的错误,它不是。

我在这里做错了什么?

错误:

Mockery\Exception\InvalidCountException : 来自 Mockery_2_Embed_Embed 的方法 create() 应该被准确调用 1 次,但被调用 0 次。

测试:

class OEmbedTest extends TestCase

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

    /**
     * It can return an OEmbed object
     * @test
     */
    public function it_can_return_an_o_embed_object()
    
        $url = 'https://www.youtube.com/watch?v=9hUIxyE2Ns8';

        Mockery::mock(Embed::class)
            ->shouldReceive('create')
            ->with($url)
            ->once();

        $response = $this->postJson(route('oembed', ['url' => $url]));
        $response->assertSuccessful();
    

控制器:

public function __invoke(Request $request)

    $info = Embed::create($request->url);

    $providers = $info->getProviders();

    $oembed = $providers['oembed'];

    return response()
        ->json($oembed
            ->getBag()
            ->getAll());

【问题讨论】:

不需要测试嵌入包;已经是tested了,对吧? 是的,所以我的想法是模拟该类的响应。 提示:您可以将__invoke 方法重命名为handle,它看起来会更好。您正在 (invoke) 方法中创建 Embed 的一个实例;没有模拟,那是无法测试的。我个人避免嘲笑任何东西并诉诸存根/假类等。我建议让 Laravel 在它到达控制器之前构建/创建/实例化 Embed 对象。这意味着创建一个服务提供者,它将负责为您实例化 Embed 对象,并可能将其放置在 $request 本身中。请添加有关您正在访问的路线的更多信息。 所以你基本上是说我应该依赖注入类?喜欢$this->app->bind()。当然这是可能的,但如果没有它,它不应该是可能的吗?因为那时我需要一个接口等,这对于这个小测试来说感觉很麻烦。 你不需要接口,直接使用->bind(Embed\Embed::class, function($app) return Embed\Embed::create($app['request']->url); ); 现在不知道create 被调用后会发生什么。但是在控制器中你现在可以使用$info = resolve(Embed\Embed::class);显然,您需要检查请求是否具有 url 参数等(在绑定闭包中执行此操作)。现在在测试setUp 你需要重新绑定它,更多信息请阅读***.com/questions/50262576/… 【参考方案1】:

您似乎以错误的方式嘲笑Embed 类。如果你使用 Laravel 门面方法 shouldReceive() 而不是创建类本身的 Mock,框架会为你将 mock 放入服务容器中:

Embed::shouldReceive('create')
    ->with($url)
    ->once();

而不是

Mockery::mock(Embed::class)
    ->shouldReceive('create')
    ->with($url)
    ->once();

另外请注意,如果您的测试代码传递给 mock 的参数与您通过 with($url) 学习到的 mock 不同,则 mock 认为自己未调用。但是无论如何调用未定义的方法,您都会收到另一个错误。

【讨论】:

Embed 类不是 Facade。它的命名空间是 Embed\Embed。 所以你想模拟一个静态方法?恐怕这行不通。 不知道不使用 Facades 就不能模拟静态方法。 这实际上是模拟库的限制,而不是 Laravel。 Facades 只提供对方法的静态访问;这并不意味着方法本身是静态的(事实上,据我所知,它们从来都不是)。因此,实现可测试性的最简单方法可能是围绕它构建自己的代理类......(不使用静态方法)。【参考方案2】:

我能够通过在我的测试中使用它来解决这个问题:

protected function setUp()

    parent::setUp();

    app()->instance(Embed::class, new FakeEmbed);

然后这样解决

$embed = resolve(Embed::class);
$embed = $embed->create($url);

【讨论】:

所以你创建了另一个类(FakeEmbed)来模拟? 是的,这是最简单的方法。

以上是关于Laravel + Mockery InvalidCountException的主要内容,如果未能解决你的问题,请参考以下文章

Mockery 和 Laravel 构造函数注入

这个模拟对象上不存在方法 - Laravel,Mockery

Laravel & Mockery - 单元测试关系数据

Laravel 5 - 使用 Mockery 模拟 Eloquent 模型

如何使 Mockery 重载选项在 Laravel 5 上正常工作

使用 Mockery Eloquent 模型模拟 Laravel