0

I'm still in the process of learning about Laravel and Dependency Injection. I understand the concept, but I don't know how to mock a dependency in this specific case:

MyController.php

use Illuminate\Routing\Controller;    
use MyPackage\Services\ServiceInterface;

class MyController extends Controller{
    protected $service;

    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }    
}

MyServiceProvider.php

use Illuminate\Support\ServiceProvider;

class MyServiceProvider extends ServiceProvider{

    public function register()
    {
        $this->app->bind('MyPackage\Services\ServiceInterface', function ($app) {
            return new MyPackage\Services\ConcreteService(['key'=>'123'], $app->make('GuzzleHttp\Client'));
        });
    }    
}

So, as you can see, I have a controller that requires an instance of ServiceInterface. That instance is being resolved in the ServiceProvider. The constructor of ConcreteService requires a client to perform Http request to an API. This Http is being resolved by the Service container (It will be an instance of Guzzle).

Now, how can I mock this instance of Guzzle on my tests?

The ideal result is doing something like this:

MyTest.php

...
$this->post(route('routeToActionInMyController'), $params);

So, in my tests I just need to hit the route that will be using an specific method of MyController.php but I don't need a "real" Guzzle instance. I just need to mock the response to test if MyController behaves in the expected way (and stores things in the database properly).

How can I instruct the Service Container to inject a Mocked object during tests only? Or am I doing this in the completely wrong way?

Any help will be appreciated.

Thanks in advance

TJ is too short
  • 827
  • 3
  • 15
  • 35

1 Answers1

3

In your test class:

class TestingSomething extends TestCase {
     protected function setUp() {
          parent::setUp();
          $mockServiceInterface = $this->getMockBuilder(ServiceInterface::class)->getMock();
          $this->app->instance(ServiceInterface::class,$mockServiceInterface);
     }

     public function testPostToRoute() {
        $this->post(route('routeToActionInMyController'), $params);
    }
 } 

This should replace what's already bound in the service container with that mock instance.

Refer to the PHPUnit manual on chapter 9. Test doubles for what you can do with the mock builder and resulting mocks.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
  • Thanks for your reply @apokryfos. This means that I need to replace the entire binding? I mean, I cannot replace only the Guzzle binding? – TJ is too short Oct 09 '17 at 14:06
  • If you instantiate guzzle via dependency injection then you can mock the guzzle client. That should work too – apokryfos Oct 09 '17 at 14:07
  • I think Guzzle has this out of the box. Check http://docs.guzzlephp.org/en/stable/testing.html – apokryfos Oct 09 '17 at 14:10
  • I think I'm doing that properly. Can you please tell me if it is correct? This is what I'm doing: The constructor of the ConcreteService receives a parameter which must be an instance of Guzzle. And in the Service Provider I'm passing (injecting) that parameter by doing: $app->make('GuzzleHttp\Client') – TJ is too short Oct 09 '17 at 14:11
  • Yes you're doing that correctly so in your test setup you can do `$this->app->bind('GuzzleHttp\Client', function () { /* code from http://docs.guzzlephp.org/en/stable/testing.html here */ })` – apokryfos Oct 09 '17 at 14:16
  • Sorry, there are two things: 1) I cannot do this on the setUp() method. I would like to be able to test multiple scenarios in the same class (like providing invalid data to see what is the behaviour of my app). and 2) For some reason the Guzzle client is not being replaced with the mocked instance (I'm doing exactly as in the Guzzle documentation). Any suggestions? – TJ is too short Oct 09 '17 at 15:44
  • @TJistooshort unfortunately you have to either do this in the setup or inside the test itself. If you do this earlier then the service container will be overwritten. As for b) I think the guzzle docs only show a way to replace the handler with a mock one which doesn't do any requests (And is faster) . If that doesn't work for you then you should mock the guzzle client itself . – apokryfos Oct 09 '17 at 15:50