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

5
我正在使用通过GitHub APILaravel API包装器。我创建了一个依赖注入类。如何模拟App\Http\GitHub.php类中的exists方法? 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个回答

2
您的问题在于当您初始化GitHub对象时,没有引用服务容器中的对象。
        // 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 生命周期中随时引用它们。这种模式使我们能够以清晰的方式进行依赖注入等操作,因此我们可以测试类被调用的情况,因为我们可以用任何我们想要的东西“替换”盒子里的内容。
Laravel使用mocking functions将上述所有内容抽象化处理。个人而言,我只使用spy来代替其他功能,这样我就不必记住其他功能所做的事情(尽管有些情况下您需要使用其他功能)。
现在来看解决方案:
    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);

2
这里有三个问题,一个是你实例化对象的方式。另外你在调用模拟对象的两个方法时绑定到了错误的实例上。
依赖注入
通常静态方法是一种反模式,构造函数参数不适用于容器运作的方式,因此您将无法使用 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);

绑定实例

与容器一起工作时,它会根据类自动加载它,因此以下代码将在使用app()resolve()或构造函数解决依赖项时自动注入GithubManager

public function __construct(GithubManager $manager)

这段代码将在我的上面的resolve示例中注入GithubManager,但是在您的示例中,您将其绑定到GitHub类,这不会自动加载,您应该始终模拟链条最远处的类。因此,您的实例绑定应该如下所示。
app()->instance(GitHubManager::class, $m);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接