Laravel: 如何模拟依赖注入 class 方法
Laravel: how to mock dependency injection class methods
我正在使用 GitHub API through a Laravel API Wrapper。我创建了一个依赖注入 class。如何在 App\Http\GitHub.php
class 中模拟 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
值,我在这里做错了什么?
您的问题是,当您初始化 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 之类的事情,因此我们可以测试何时调用 类 因为我们可以 "swap" 随心所欲地找出盒子里的东西.
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);
这里有树问题,你实例化你的对象的方式。您在模拟对象上调用两个方法并将其绑定到错误实例的方式。
依赖注入
静态方法通常是一种反模式,构造函数参数不适用于容器的工作方式,因此您将无法使用 resolve(Github::class);
。通常 Laravel
class 使用 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);
正在绑定实例
使用容器,它会根据 classes 自动加载它,所以下面的代码将依赖注入 GithubManager
如果解析为 app()
,resolve()
或在构造函数中。
public function __construct(GithubManager $manager)
此代码将在我上面的解析示例中注入 GithubManager,但在您的示例中,您将其绑定到 GitHub class,它不会自动加载,您应该始终模拟 class 最远的链。因此你的实例绑定应该是。
app()->instance(GitHubManager::class, $m);
我正在使用 GitHub API through a Laravel API Wrapper。我创建了一个依赖注入 class。如何在 App\Http\GitHub.php
class 中模拟 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
值,我在这里做错了什么?
您的问题是,当您初始化 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 之类的事情,因此我们可以测试何时调用 类 因为我们可以 "swap" 随心所欲地找出盒子里的东西.
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);
这里有树问题,你实例化你的对象的方式。您在模拟对象上调用两个方法并将其绑定到错误实例的方式。
依赖注入
静态方法通常是一种反模式,构造函数参数不适用于容器的工作方式,因此您将无法使用 resolve(Github::class);
。通常 Laravel
class 使用 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);
正在绑定实例
使用容器,它会根据 classes 自动加载它,所以下面的代码将依赖注入 GithubManager
如果解析为 app()
,resolve()
或在构造函数中。
public function __construct(GithubManager $manager)
此代码将在我上面的解析示例中注入 GithubManager,但在您的示例中,您将其绑定到 GitHub class,它不会自动加载,您应该始终模拟 class 最远的链。因此你的实例绑定应该是。
app()->instance(GitHubManager::class, $m);