phplaravelphpunitguzzlemockery

Laravel - Mock single method in a command using PHP UNIT


I'm trying to test a command where there's a method I want to mock because it makes a call to an external service using Guzzle, but no matter what I try, I can't seem to mock it successfully. It always calls the original method. Any ideas? This is my command (relevant code):

class CommandToTest extends Command
{

    //constants, setup handle and other stuff

    //other methods

    protected function methodToMock()
    {
        //method with the call to external web service that should be mocked
    }

    //other methods
}

Test:

class TestForCommand extends TestCase
{
    //constants etc

    public function testCommandToTest()
    {
        $commandMock = Mockery::mock(CommandToTest::class)->makePartial();

        $commandMock->shouldAllowMockingProtectedMethods()->shouldReceive('methodToMock')
            ->andReturn([
                'Test01' => 'T01',
                'Test02' => 'T02',
                'Test03' => 'T03'
            ]);

        $this->app->instance(CommandToTest::class, $commandMock);

        //other stuff    

        $this->artisan('tools:commandtotest', [//params])
            ->expectsConfirmation(//params ok)
            ->assertOk();
    }

    //assertions
}

But no matter what I try, the real method is always called. How can I make this work? Should I approach it from another angle? Thanks.


Solution

  • If you are writing tests for the command, you should be mocking the http client so that it returns a mocked response instead of mocking the command itself.

    Simplest way to achieve this is to switch to Laravel's Http Client. This http client was built with testing in mind so if you use it, mocking a response would be as simple as

    Http::fake([
        // Stub a JSON response for GitHub endpoints...
        'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),
     
        // Stub a string response for Google endpoints...
        'google.com/*' => Http::response('Hello World', 200, $headers),
    ]);
    

    Alternatively, if you must stick with guzzle, then you should inject it or resolve it in your method so that you use dependency injection

    class CommandToTest extends Command
    {
    
        //constants, setup handle and other stuff
    
        //other methods
    
        protected function methodToMock()
        {
            //method with the call to external web service that should be mocked
            $client = resolve(\GuzzleHttp\Client::class);
            $response = $client->post('something');
        }
    
        //other methods
    }
    

    Then you may inject the mock to the container inside your tests

    $this->app->instance(\GuzzleHttp\Client::class, $guzzleMock);