Laravel:如何模拟依赖注入类方法

时间:2020-02-21 16:26:46

标签: php laravel mocking github-api mockery

我正在使用GitHub APILaravel API Wrapper。我创建了一个依赖注入类。如何在exists类中模拟App\Http\GitHub.php方法?

App\Http\GitHub.php

use GrahamCampbell\GitHub\GitHubManager;

class Github
{
    public $username;

    public $repository;

    public function __construct($username, $repository, GitHubManager $github)
    {
        $this->username = $username;

        $this->repository = $repository;

        $this->github = $github;
    }

    public static function make($username, $repository)
    {
        return new static($username, $repository, app(GitHubManager::class));
    }

    /**
     * Checks that a given path exists in a repository.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path)
    {
        return $this->github->repository()->contents()->exists($this->username, $this->repository, $path);
    }
}

测试:

    use App\Http\GitHub;
    public function test_it_can_check_if_github_file_exists()
    {
        $m = Mockery::mock(GitHub::class);
        $m->shouldReceive('exists')->andReturn(true);
        app()->instance(GitHub::class, $m);

        $github = GitHub::make('foo', 'bar');

        $this->assertTrue($github->exists('composer.lock'));
    }

运行此测试实际上会命中API,而不仅仅是返回模拟的true值,我在这里做什么错了?

2 个答案:

答案 0 :(得分:1)

您的问题是,初始化github对象时,您没有在Service Container中引用该对象。

        // Initialises an object in the service container.
        app()->instance(GitHub::class, $m);

        // Creates a new object from the class and doesn't use the one in the container.
        $github = GitHub::make('foo', 'bar');

服务容器本质上是一个包含所有初始化对象的盒子,您可以在Laravel生命周期中随时引用它们。这种模式使我们能够以干净的方式执行诸如Dependency Injection之类的事情,因此我们可以测试何时调用类,因为我们可以用所需的任何东西“交换”盒子中的内容。

Laravel通过使用mocking functions为我们抽象了以上所有内容。就我个人而言,我只是对所有事情都使用间谍,因此不必记住其他人的所作所为(当然,在某些情况下您需要使用其他人)。

现在是解决方案:

    public function test_it_can_check_if_github_file_exists()
    {
        // Initialise GitHub::class into the service container
        $gitHubSpy = $this->spy(GitHub::class);

        // Mock the function
        $gitHubSpy->shouldReceive('exists')
            ->andReturn(true);

        // Assert we have mocked correctly
        $this->assertTrue($gitHubSpy->exists('composer.lock'));
    }

在现实世界中,您最有可能想断言您的生产代码调用了可以通过以下操作完成的功能:

$gitHubSpy->shouldReceive('exists')->with('composer.lock')->andReturn(true);

答案 1 :(得分:1)

这里存在树问题,即实例化对象的方式。在模拟对象上调用两个方法并将其绑定到错误实例的方式。

依赖注入

静态方法通常是一种反模式,构造函数参数与容器的工作方式不兼容,因此您将无法使用resolve(Github::class);。通常Laravel类通过使用setter来解决此问题。

class Github
{
    public $username;

    public $repository;

    public $github;

    public function __construct(GitHubManager $github)
    {
        $this->github = $github;
    }

    public function setUsername(string $username) {
        $this->username = $username;

        return $this;
    }

    public function setRepository(string $repository) {
        $this->repository = $repository;

        return $this;
    }
}

现在,您可以使用以下方法来调用代码。

resolve(Github::class)->setUsername('Martin')->setRepository('my-repo')->exists();

方法链

这里有两个对模拟对象的调用,它们是链接的,因此您应该创建与此相似的模拟链。现在,模拟对象将不知道内容,因此会失败。

$m = Mockery::mock(GitHub::class);

$m->shouldReceive('contents')
    ->andReturn($m);

$m->shouldReceive('exists')
    ->with('Martin', 'my-repo', 'your-path')
    ->once()
    ->andReturn(true);

绑定实例

使用容器,它将基于类自动加载它,因此,如果使用GithubManagerapp()或在构造函数中进行解析,则以下代码将依赖注入resolve()。 / p>

public function __construct(GithubManager $manager)

此代码将在上面的我的解析示例中注入GithubManager,但是在您的示例中,您将其绑定到GitHub类,该类不会自动加载,并且您应始终在链中最远的地方对该类进行模拟。因此,实例绑定应该是。

app()->instance(GitHubManager::class, $m);