【问题标题】:Mocking a Laravel Command Dependency模拟 Laravel 命令依赖
【发布时间】:2019-12-25 16:33:03
【问题描述】:

正如官方 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 的方法和建议,而不是 question 中使用的方法,并且我使用依赖注入来将我的模型作为服务插入。

所以我同时认为对它进行单元测试是个好主意:

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 模型?

【问题讨论】:

    标签: php laravel command phpunit laravel-5.7


    【解决方案1】:

    首先,您需要创建模型报告类的模拟,然后需要将其绑定到容器。这样,每当您在命令类中调用报告模型类时,您将拥有一个模拟模型类,其中包含您所期望的特定响应。

    $this->app->instance(Report::class, \Mockery::mock(Report::class, function($mock){
                $mock->shouldReceive('getAnualReport')->andReturn(['headers'=>'any values', 'data'=>'any values']);
            }));
    

    【讨论】:

    • 还有我怎样才能拆除模拟服务?
    • 您可以在 laravel 的每个测试类中使用 tearDown 方法,当您使用该方法并调用 parent::tearDown();它会自动清除所有模拟对象。但我记得删除模拟对象有一些问题。你也可以看看这个链接docs.mockery.io/en/latest/reference/phpunit_integration.html
    【解决方案2】:

    官方的方式是:

    $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;
    }
    

    单元测试与集成测试

    顺便说一句,这个测试不是单元测试,而是集成测试。如果您包含一个框架依赖项(如本例中的命令),它是一个集成测试(您的业务逻辑和框架之间的集成)。所以我的建议是把你的业务逻辑写在一个单独的服务中,然后把这个服务注入到命令中。然后您可以单独对服务进行单元测试。

    【讨论】:

      猜你喜欢
      • 2022-01-09
      • 1970-01-01
      • 2015-10-21
      • 2016-10-05
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      • 1970-01-01
      • 2020-06-06
      相关资源
      最近更新 更多