测试时如何模拟整个应用程序的 class 方法?

How to mock class method for whole application when testing?

我有 ServiceApi.php - 在构造函数中它有默认的 guzzle 客户端:

$this->client = new Client($options);

否则它有方法:

public function fetch()
{
return $this->client->get('http://......')->getBody()->getContents();
}

另一个 class ServiceUser.php - 有使用 ServiceApi 的方法:

public function fetchFromApi()
{
return (new ServiceApi())->fetch();
}

当我 运行 测试时,我想要 (new ServiceUser())->fetchFromApi() - 不要调用我在测试中硬编码的真实 api 和 return 预定义答案。

试图在测试中模拟 ServiceApi,但它只在测试方法中工作,当通过 ServiceUser 调用时它会变成真实的 api。

这样做是真的吗? 或者我试图做一些不可能的事情或者这个代码结构不符合测试目的?

您需要了解 Dependency Injection and Service Container 个概念。满足您的需求:

class ServiceApi {
  public function __construct(Client $client)
  {
     $this->client = $client;
  }
}

class ServiceUser {
  public function __construct(ServiceApi $api)
  {
     $this->api = $api;
  }
}

并在AppServiceProvider中配置Client:

public function register()
{
  $this->app->bind(ServiceApi::class, function($app){
    //I don't know where from you get options
    $options = [];
    $client = new Client($options);
    return new ServiceApi($client);
  });
}

现在,在测试中你可以这样做:

public function testFetch()
{
  $mock = \Mockery::mock(ServiceApi::class);
  $mock->shouldReceive('fetch')->once();
  $this->instance(ServiceApi::class, $mock);
  //now test
}

像马克西姆说的那样实现了。

应用服务供应商:

$this->app->bind(ApiInterface::class, function($app, $params){
    switch ($params['account']->type) {
        case 'first':
            $class = 'App\Classes\FirstApi';
            break;
        case 'second':
            $class = 'App\Classes\SecondApi';
            break;
        default:
            throw new \Exception('unknown account type');
    }
    return new $class($params['account']);
});

UseApi 特征:

public function api()
{
    return \App::makeWith(ApiInterface::class, ['account' => $this->account]);
}

但是在测试中进行模拟时,我遇到了一些问题,导致服务提供商中的参数绑定。

测试:

// does we need mock FirstApi instead ApiInterface?
// But working only with FirstApi
$mock = \Mockery::mock(FirstApi::class)->makePartial();

    $mock->shouldReceive('methodApi') // mock methodApi
        ->once()
        ->andReturn('foo');

// $this->instance(......) does't work - I think it's bindings issue, 
// replaced it with bind() 
$this->app->bind(ApiInterface::class, function() use ($mock){
        return $mock;
    });

    $result = $model->methodApi();

    $this->assertEquals('foo',$result);

现在它过去了!