'Trying to mock an http client that is inside a controller, using phpunit, but it doesn't work

I have to test the routes of a controller which uses Guzzle Wrapper as a client to get data from an API. The app uses Laravel as a framework.

This is the part of the code that gives me trouble in the controller's function I am currently testing:

public function addContacts(Request $request)
{
   ...
   $client = new GuzzleWrapper();
   $response = $client->post($uri, $data);
   if($response->getStatusCode() != 200) { 
      return response()->json("Problem getting data", 500);
   }
   ...
}

Now, what I have tried are getMockBuilder:

$mock = getMockBuilder(GuzzleWrapper::class)->onlyMethods(array('post'))->getMock();
$mock->expects($this->once())->method('post')->willReturn(response()->json([$responseData], 200));

Http::fake :

Http::fake(
   [$uri => Http::response([$responseData], 200)
);

Mockery :

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class);
$mockGuzzleClient->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));

I also tried Mockery like this:

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
   $mock->shouldReceive('post')
   ->andReturn(response()->json([$responseData], 200));
});

And like this:

$this->app->instance(
   GuzzleWrapper::class,
   Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
      $mock->shouldReceive('post')
      ->andReturn(response()->json([$responseData], 200));
   })
);

and following everything I tried is the call to test my controller's function:

//Successfully add contacts to list
$this->json('POST', $addContactUri, $input_data, $token);
$this->seeStatusCode(201);

Now! Whatever I tried, it's as if the GuzzleWrapper is never mocked, it still does the post and doesn't return status code 200. No matter what I find on google, it never fits with this scenario... Can anyone help me?



Solution 1:[1]

Mocking is based on the container, for Laravel to pick up your mocked classes, you should never use the new keyword. Instead use the container by using resolve().

$client = resolve(GuzzleWrapper::class);

This should work with one of the following mock approach where you use Mockery::mock(). But the way you are mocking the response, without seing the GuzzleWrapper, i would not expect it to return a Laravel response or else it is custom code.

$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
   $mock->shouldReceive('post')
        ->andReturn(response()->json([$responseData], 200));
});

The most correct way would to use the Http facade. Your call in the controller should look like this.

Http::post($uri, $data);

The mocking should look like this, in general it seems like you are combining guzzle and Http interchangeable in your mocking attempts and that wont fly. If you mock with Http use the Http facade.

Http::fake([
    $uri => Http::response(['your data' => 'response'], 200, []),
]);

Solution 2:[2]

What if you convert the GuzzleWrapper to a dependency? What I mean is to declare the GuzzleWrapper as the private property of the Controller, this way, it would be easier to test the controller: you need to pass the proper GuzzleWrapper to the constructor.

class ContactController {
    private GuzzleWrapper $client;

    public function __construct(GuzzleWrapper $client)
    {
        $this->client = $client;
    }

    public function addContacts(Request $request)
    {
       ...
       $response = $this->client->post($uri, $data);
       if($response->getStatusCode() != 200) { 
          return response()->json("Problem getting data", 500);
       }
       ...
    }
}

Once you have done this, it should be easier to test. Then, if you need to test the client, I would recommend either the PHPUnit Mocks or Mockery.

If you can avoid testing the client (maybe you already tested it properly in other places), then I would recommend looking at my composer package https://packagist.org/packages/doppiogancio/mocked-client.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mrhn
Solution 2 Karl Hill