模拟 Laravel 命令依赖
Mocking a Laravel Command Dependency
正如官方Laravel的documentation所说,我做了以下命令:
namespace App\Console\Commands;
use App\Model\Report;
use Illuminate\Console\Command;
use Exception;
class ExportAnualReport extends Command
{
/**
* @var string
*/
protected $description = "Print Anual Report";
/**
* @var string
*/
protected $signature = "report:anual";
public function __construct()
{
parent::__construct();
}
public function handle(Report $report): int
{
//@todo Implement Upload
try {
$reportData = $report->getAnualReport();
$this->table($reportData['headers'], $reportData['data']);
return 0;
} catch (Exception $e) {
$this->error($e->getMessage());
return 1;
}
}
}
但我已经遵循 laravel 的方法和建议,而不是此 中使用的方法,并且我利用依赖注入将我的模型作为服务插入。
所以我同时认为对它进行单元测试是个好主意:
namespace Tests\Command;
use App\Model\Report;
use Tests\TestCase;
class TripAdvisorUploadFeedCommandTest extends TestCase
{
public function setUp()
{
parent::setUp();
}
public function testFailAnualReport()
{
$this->artisan('report:anual')->assertExitCode(1);
}
public function testSucessAnualReport()
{
$this->artisan('report:anual')->assertExitCode(0);
}
}
但在我的例子中,我已经通过 handle
函数将 Eloquent 模型 Report
注入到我的命令中,所以我想模拟 Report
对象实例而不是点击实际的数据库。
根据记录,Report
对象如下:
namespace App\Model
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Report extends Model
{
/**
* @var string
*/
protected $table = 'myapp_report_records';
/**
* @var string
*/
protected $primaryKey = 'report_id';
public function getAnualReport()
{
$now=Carbon::now();
$oneYearBefore=new Carbon($now);
$oneYearBefore->modify('-1 year');
$results=$this->where('date','>',$oneYearBefore)->where('date','<',$now)->all();
if(empty($results)){
throw new ModelNotFoundException();
}
return $results;
}
}
那么我如何模拟提供的 Report
模型?
首先,您需要创建模型报告的模拟 class,然后您需要将其绑定到容器。这样,每当您在命令 class 中调用报告模型 class 时,您都会得到一个模拟模型 class,其中包含您期望的特定响应。
$this->app->instance(Report::class, \Mockery::mock(Report::class, function($mock){
$mock->shouldReceive('getAnualReport')->andReturn(['headers'=>'any values', 'data'=>'any values']);
}));
官方的方式是:
$this->mock(Report::class, function($mock){
$mock->shouldReceive(.....)
->withArgs([...])
->andReturn(....);
});
引擎盖下
在幕后,此方法执行以下操作:
protected function mock($abstract, Closure $mock = null)
{
return $this->instance($abstract, Mockery::mock(...array_filter(func_get_args())));
}
protected function instance($abstract, $instance)
{
$this->app->instance($abstract, $instance);
return $instance;
}
单元测试与集成测试
顺便说一句,这个测试不是单元测试,而是集成测试。如果您包含框架依赖项(如本例中的命令),则它是一个集成测试(您的业务逻辑与框架之间的集成)。所以我的建议是将你的业务逻辑写在一个单独的服务中,然后将这个服务注入到命令中。然后你可以单独对服务进行单元测试。
正如官方Laravel的documentation所说,我做了以下命令:
namespace App\Console\Commands;
use App\Model\Report;
use Illuminate\Console\Command;
use Exception;
class ExportAnualReport extends Command
{
/**
* @var string
*/
protected $description = "Print Anual Report";
/**
* @var string
*/
protected $signature = "report:anual";
public function __construct()
{
parent::__construct();
}
public function handle(Report $report): int
{
//@todo Implement Upload
try {
$reportData = $report->getAnualReport();
$this->table($reportData['headers'], $reportData['data']);
return 0;
} catch (Exception $e) {
$this->error($e->getMessage());
return 1;
}
}
}
但我已经遵循 laravel 的方法和建议,而不是此
所以我同时认为对它进行单元测试是个好主意:
namespace Tests\Command;
use App\Model\Report;
use Tests\TestCase;
class TripAdvisorUploadFeedCommandTest extends TestCase
{
public function setUp()
{
parent::setUp();
}
public function testFailAnualReport()
{
$this->artisan('report:anual')->assertExitCode(1);
}
public function testSucessAnualReport()
{
$this->artisan('report:anual')->assertExitCode(0);
}
}
但在我的例子中,我已经通过 handle
函数将 Eloquent 模型 Report
注入到我的命令中,所以我想模拟 Report
对象实例而不是点击实际的数据库。
根据记录,Report
对象如下:
namespace App\Model
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon
use Illuminate\Database\Eloquent\ModelNotFoundException;
class Report extends Model
{
/**
* @var string
*/
protected $table = 'myapp_report_records';
/**
* @var string
*/
protected $primaryKey = 'report_id';
public function getAnualReport()
{
$now=Carbon::now();
$oneYearBefore=new Carbon($now);
$oneYearBefore->modify('-1 year');
$results=$this->where('date','>',$oneYearBefore)->where('date','<',$now)->all();
if(empty($results)){
throw new ModelNotFoundException();
}
return $results;
}
}
那么我如何模拟提供的 Report
模型?
首先,您需要创建模型报告的模拟 class,然后您需要将其绑定到容器。这样,每当您在命令 class 中调用报告模型 class 时,您都会得到一个模拟模型 class,其中包含您期望的特定响应。
$this->app->instance(Report::class, \Mockery::mock(Report::class, function($mock){
$mock->shouldReceive('getAnualReport')->andReturn(['headers'=>'any values', 'data'=>'any values']);
}));
官方的方式是:
$this->mock(Report::class, function($mock){
$mock->shouldReceive(.....)
->withArgs([...])
->andReturn(....);
});
引擎盖下
在幕后,此方法执行以下操作:
protected function mock($abstract, Closure $mock = null)
{
return $this->instance($abstract, Mockery::mock(...array_filter(func_get_args())));
}
protected function instance($abstract, $instance)
{
$this->app->instance($abstract, $instance);
return $instance;
}
单元测试与集成测试
顺便说一句,这个测试不是单元测试,而是集成测试。如果您包含框架依赖项(如本例中的命令),则它是一个集成测试(您的业务逻辑与框架之间的集成)。所以我的建议是将你的业务逻辑写在一个单独的服务中,然后将这个服务注入到命令中。然后你可以单独对服务进行单元测试。