Home > Net >  How to mock class method for whole application when testing?
How to mock class method for whole application when testing?

Time:11-03

I have ServiceApi.php - in constructor it have guzzle client with defaults:

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

else it have method:

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

Another class ServiceUser.php - has method which using ServiceApi:

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

When I run test I want (new ServiceUser())->fetchFromApi() - don't call real api and return predefined answer which I hardcoded in test.

Tried to mock ServiceApi in test but it working only in test method, when calling via ServiceUser it going to real api.

Does it real to do this? Or I trying to do something impossible or this code structure does't meet testing purposes?

CodePudding user response:

You need to understand Dependency Injection and Service Container concepts. For your needs:

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

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

And configure Client in AppServiceProvider:

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

And now, in test you can do this:

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

CodePudding user response:

Realised it as Maksim says.

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 trait:

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

but when mocking in test I have some troubles cause param bindings in service provider.

test:

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

and now it's passing!

  • Related