【问题标题】:How to mock class method for whole application when testing?测试时如何模拟整个应用程序的类方法?
【发布时间】:2021-12-16 20:16:19
【问题描述】:

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

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

否则它有方法:

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

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

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

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

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

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

【问题讨论】:

  • 我建议你使用Http门面,看看this

标签: laravel mocking phpunit guzzle


【解决方案1】:

您需要了解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
}

【讨论】:

  • 我是这么想的,但是如果我需要将Account $acc 传递给构造函数来配置guzzle,一些令牌等如何处理。它总是动态的。我需要在哪里放置这些设置?在注册中我不能导致 $acc 没有定义
  • 马克西姆,我们明天可以在电报中讨论吗?我可以付费咨询)
  • 好的,实现方法withConfig(array $config): self并动态传递config作为链$this->api->withConfig($config)->fetch()
  • 意识到了,你能检查代码中的问题吗?谢谢 - 这是有效的:)
【解决方案2】:

按照马克西姆的说法实现了。

AppServiceProveder:

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

现在它过去了!

【讨论】:

    猜你喜欢
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    相关资源
    最近更新 更多