【问题标题】:Laravel: how to mock dependency injection class methodsLaravel:如何模拟依赖注入类方法
【发布时间】:2020-06-06 02:58:51
【问题描述】:

我通过Laravel API Wrapper 使用GitHub 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 值,我在这里做错了什么?

【问题讨论】:

    标签: php laravel mocking github-api mockery


    【解决方案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);
    

    绑定实例

    使用容器时,它将根据类自动加载它,因此如果使用app()resolve() 或在构造函数中解析,以下代码将依赖注入GithubManager

    public function __construct(GithubManager $manager)
    

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

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

    【讨论】:

      【解决方案2】:

      您的问题是,当您初始化 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');
      

      Service Container 本质上是一个包含所有已初始化对象的盒子,您可以在 Laravel 生命周期中的任何时候引用它们。这种模式允许我们以干净的方式执行诸如Dependency Injection 之类的操作,因此我们可以测试何时调用类,因为我们可以用我们想要的任何东西“交换”盒子中的内容。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-02
        • 1970-01-01
        • 2017-01-22
        • 2021-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多