Laravel - 利用Real-time Facade缩短测试中Mock类的代码
Inspiration
这篇文章启发自Laravel News最近的一篇文章Defense Programming: Anticipating Failures with Tests。
翻译下来应该是这样的:《防御式编程:参与到失败的测试中去》。看了看整篇文章,大概讲的就是一种测试第三方依赖的方法,而Laravel框架自5.5版本后有一种Real-time Facade,可以更加简化此类测试在Laravel中的写法。
Doc Abstract
随着现代企业技术的分工,大而全的应用和项目不复存在。“微服务”化的服务分拆、所谓“服务治理”、各种高大上的架构,大部分都基于Unix最原始的哲学“程序应该只关注一个目标,并尽可能把它做好”。
所以,当我们新开发一个功能的时候,很可能就会用到另外的服务。比如Laravel News这个网站,它的首页上面有些工作信息是来自于LaraJobs这个第三方网站的,而当LaraJobs这个网站服务不可用或停掉了之后,Laravel News这个网站的首页会发生些什么呢?
所以我们需要写一些服务失败时候的测试用例,以便我们更好的应对我们的功能。
Laravel中可以使用real-time facades对预期失败的服务进行测试,文中对“获取文章列表”这一Http服务进行举例。
假设“获取文章列表”这一服务为简单的Http服务,那么作为调用方,文中是通过注册一个单独的服务项进行的。
<?php
namespace App\Contracts;
interface ArticleRepository
{
public function get();
}
<?php
namespace App\Repositories;
use GuzzleHttp\Client;
class ApiArticleRepository implements ArticleRepository
{
public function __construct(Client $client)
{
$this->client = $client;
}
public function get($id)
{
return $this->client->get('posts', ['query' => ['id' => $id]]);
}
}
通过注册ServiceProvider到容器中来保证单例:
<?php
namespace App\Providers;
use GuzzleHttp\Client;
class RepositroyServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function register()
{
$this->app->singleton('PostRepository',
function() {
return new \App\Repositories\ApiArticleRepository(new Client([
'base_uri' => config('api.url')
]));
});
}
}
应用中使用这个服务的例子如下:
<?php
namespace App\Http\Controllers;
use App\Repositories\ApiArticleRepository as Repository;
class RegisterController extends Controller
{
protected $repository;
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
public function view($id)
{
return view('artice.view', ['article' => $this->repository->get($id)]);
}
}
那么,如何编写测试用例来测试失败的情况呢?
应该注意到,我们应该测试$this->repository->get($id)
这段代码在Http请求出错时候的情况。
real-time facades可以帮助我们进行类似mock的测试,这在进行测试或mock数据的时候很有用。
那么我们需要将ApiArticleRepository改写成real-time facades的方式。
<?php
namespace App\Repositories;
use Facades\GuzzleHttp\Client;
class ApiArticleRepository implements ArticleRepository
{
public function get($id)
{
return Client::get('posts', ['query' => ['id' => $id]]);
}
}
这样,我们就可以通过real-time facades来进行我们想要进行的测试。
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/**
* Description of ClientTest
*
* @encoding UTF-8
* @author jiaojie <jiaojie@didichuxing.com>
* @since 2018-02-27 11:12 (CST)
* @version 0.1
* @description
*/
class ClientTest extends TestCase
{
/**
*
* @test
*/
public function testing_guzzle_exception()
{
\Facades\GuzzleHttp\Client::shouldReceive('get')->andThrow(
new \GuzzleHttp\Exception\RequestException(
"Error Communicating with Server", new \GuzzleHttp\Psr7\Request('GET', 'test')
)
);
// $this->expectException(\GuzzleHttp\Exception\RequestException::class);
$this->expectException(\GuzzleHttp\Exception\BadResponseException::class);
// $response = \Facades\GuzzleHttp\Client::get('/');
// $this->assertEquals(get_class($response), '1');
// $this->assertStatus(500);
$repo = resolve('PostRepository');
$repo->get(1);
}
}
Thoughts
使用real-tiem facades对外部服务进行测试,缩短了原来PHPUnit中使用mock/stub这样的代码,也有利于对故障排查的测试。