【问题标题】:Using chained partial mocks on an interface used in an artisan command在工匠命令中使用的接口上使用链接的部分模拟
【发布时间】:2019-06-14 23:02:23
【问题描述】:

我正在尝试在 Laravel 5.3 中对工匠命令进行单元测试。该命令调用作为接口提供给命令构造函数的类中的函数。该接口调用另一个类中的函数。这是一般设置。

class MyCommand
{
    public function __construct(MyRepositoryInterface $interface)
    {
        ...
        $this->interface = $interface;
        ...
    }

    public function fire()
    {
        $this->interface->useTheSecondClass();
    }
}

class MyRepository implements MyRepositoryInterface
{
    public function __construct(MySecondRepositoryInterface $second)
    {
        ...
        $this->second = $second;
        ...
    }

    public function useTheSecondClass()
    {
        $response = $this->second->getSomeValue();
    }
}

class MySecondRepository implements MySecondRepositoryInterface
{
    /**
     * @return Some\External\Client
     */
    public function getExternalClient()
    {
        ....
        return $external_client;
    }

    public function getSomeValue()
    {
        $client = $this->getExternalClient();

        $something = $client->doSomething();

        Event::fire('some event based on $something`);

        return $something;
    }
}

我正在尝试模拟在 MySecondRepository -> getExternalClient() 中返回的变量,以便我可以伪造一个外部 API 调用并使用该伪造数据来测试从 MyCommand 类调用的 MySecondRepository -> getSomeValue()MyRepository -> useTheSecondClass() 功能像这样。

public function testMyCommand()
{
    $external_client_mock = Mockery::mock("Some\External\Client");
    $external_client_mock->shouldReceive("doSomething")
        ->andReturn("some values");

    $second_repository_mock = Mockery::mock("MySecondRepositoryInterface")
        ->makePartial();
    $second_repository_mock->shouldReceive("getExternalClient")
        ->andReturn($external_client_mock);

    $resource = new MyRepository($second_repository_mock);
    $this->app->instance("MyRepositoryInterface", $resource);

    $class = App::make(MyCommand::class);
    $class->fire();

    ...
}

我已经成功地使用这个完全相同的模拟链直接测试了$resource 变量(例如,直接测试$resource->useTheSecondClass(),而不是通过MyCommand),但是在这种情况下,虽然$second_repository_mock->getExternalClient() 是正确模拟的,但test 仍然期望对$second_repository_mock->getSomeValue() 有一个嘲笑的期望。由于$second_repository_mock 设置为部分模拟,我不明白为什么它仍在寻找所有要模拟的函数。

如果我删除 $external_client_mock 部分并完全模拟 $second_repository_mock 我的测试与 artisan 命令工作直接相关,但是我想测试在 getSomeValue() 中触发的事件是否已从 artisan 命令正确处理,如果我不能使用部分模拟,我就不能这样做。

有没有人知道为什么这不起作用?

【问题讨论】:

    标签: laravel-5 phpunit laravel-artisan mockery


    【解决方案1】:

    你试图模拟一个没有意义的界面。接口没有可使用的具体实现。这会导致错误的代码。只需模拟您的存储库即可。
    编辑
    只需使用以下重构运行测试,它们就会变为绿色:

    public function testMyCommand()  // phpcs:ignore
    {
        $external_client_mock = \Mockery::mock("App\Client");
        $external_client_mock->shouldReceive("doSomething")
            ->andReturn("some values");
    
        $second_repository_mock = \Mockery::mock("App\MySecondRepository[getExternalClient]")
            ->shouldIgnoreMissing();
        $second_repository_mock->shouldReceive("getExternalClient")
            ->andReturn($external_client_mock);
    
        $resource = new MyRepository($second_repository_mock);
        $this->app->instance("App\MyRepositoryInterface", $resource);
    
        $class = \App::make(\App\MyClass::class);
        $class->fire();
    }
    

    唯一的主要区别是你在倒数第二行有App\MyCommand,而不是App\MyClass

    【讨论】:

    • 我已经尝试过了。外部客户端部分仍然不会模拟,所以我无法测试它的响应。
    • 我已经编辑了测试并且它有效。试试看,方括号符号只是在后台进行部分模拟,然后 shouldIgnoreMissing 负责任何未明确模拟的方法(我猜你在代码的某处有副作用)。 @abe678
    • 就是这样!谢谢!
    猜你喜欢
    • 2019-03-14
    • 2018-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    相关资源
    最近更新 更多