3

Basically, my question is; Why does the $this->app->instance() Call work on one instance of the mocked object, but the other doesn't...

In the example below, the getGroupSingleSignOnLink function actually gets called, the other is mocked and the test passes...

TEST

namespace Tests\Feature;

use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
use App\Clients\SingleSignOnApi;
use Mockery;

class SingleSignOnTest extends TestCase
{

    private $validUrl = 'http://www.google.com';

    public function setUp()
    {
        parent::setUp();

        $single_sign_on = Mockery::mock(SingleSignOnApi::class);

        $single_sign_on->shouldReceive('getGroupSingleSignOnLink')->andReturn($this->validUrl);
        $single_sign_on->shouldReceive('getSingleSignOnLink')->andReturn($this->validUrl);

        $this->app->instance(SingleSignOnApi::class, $single_sign_on);
    }

    //THIS TEST FAILS, SingleSignOnApi Class Not Mocked
    public function testGroupAuthConnection()
    {
        $group = Group::whereNotNull('external_platform_key')->first();
        $user = $group->users()->first();

        $this->be($user);

        $group_sso = $group->groupAuthConnections()->first();
        $response = $this->get(route('sso.group.connect', ['id' => $group_sso->id]));

        $response->assertRedirect($this->validUrl);
        $response->assertSessionMissing('__danger');
    }

    //THIS TEST PASSES, The SingleSignOnApi Class is Mocked
    public function testAuthConnectionConnect()
    {
        $user = User::first();
        $this->be($user);

        $sso = $user->authConnections()->firstOrFail();
        $response = $this->get(route('sso.connect', ['id' => $sso->id]));

        $response->assertRedirect($this->validUrl);
        $response->assertSessionMissing('__danger');
    }

}

CONTROLLER FUNC - TEST MOCK WORKING

public function connect($id)
{
    $auth_connection = $this->findAuthConnection($id, Auth::user());

    $sso_client = App::make(SingleSignOnApi::class);
    $url        = $sso_client->getSingleSignOnLink($auth_connection);

    return redirect($url);
}

CONTROLLER FUNC - TEST MOCK NOT WORKING

public function connect($id)
{
    $group_ids = Auth::user()->groups()->pluck('groups.id')->toArray();

    $group_auth_connection = $this->findGroupAuthConnection($id, Auth::user());

    //This is the Mocked Object in my Test: SingleSignOnApi
    $sso_client = App::make(SingleSignOnApi::class, [$group_auth_connection->group->external_platform_key]);
    $url = $sso_client->getGroupSingleSignOnLink($group_auth_connection, Auth::user());

    return redirect($url);
}
7
  • What output makes you believe that the problem is with instance? Commented Mar 6, 2018 at 21:35
  • 1
    The output... I know because the class isn't mocked in the failing test, it makes the external call... But the other test, same class is getting mocked just fine. Commented Mar 6, 2018 at 23:01
  • Did you ever manage to get this working? I'm trying to mock some external API calls using this same method and it's just not working. Commented Mar 28, 2018 at 16:55
  • No, Sorry @daraul... We never could figure out what's going on. Commented Apr 13, 2018 at 17:47
  • 1
    I was actually able to get this working consistently with a few changes. Basically, I instantiate the third party SDK I'm using in the AppServiceProvider. Then in my tests I build a mock version of that, with fake responses, and pass that to $this->app->instance(). I'll do a longer post as an Answer when I get a few more minutes Commented Apr 13, 2018 at 17:52

1 Answer 1

1

I'll use Quickbooks as an example to illustrate how I got this to work consistently for me.

Here's my AppServiceProvider, defining a custom QuickbooksSDK Class I created:

...
    public function boot()
    {
        $this->app->bind(QuickbooksSDK::class, function($app) {
            return new QuickbooksSDK(
                new GuzzleHttp\Client([
                    'base_uri' => config('invoicing.baseUri'),
                    'timeout' => 3,
                    'headers' => [
                        'Authorization' => 'Bearer '.config('invoicing.apiKey'),
                        'Accept' => 'application/json'
                    ],
                    'http_errors' => false
                ]),
                config('invoicing.apiKey'),
                env('QUICKBOOKS_REFRESH_TOKEN'),
                env('QUICKBOOKS_CLIENT_ID'),
                env('QUICKBOOKS_CLIENT_SECRET')
            );
        });
    }

Then I created a second custom class, called the QuickbooksInvoicingDriver, that takes the instantiated SDK Class from the Service container:

public function __construct()
{
    $this->api = app(QuickbooksSDK::class);
}

Finally, in my test class, I can mock the QuickbooksSDK, with my own custom responses, to make testing easier:

    $vendorResponse = '{"Vendor": {"Balance": 0,"Vendor1099": false,"CurrencyRef": {"value": "GYD","name": "Guyana Dollar"},"domain": "QBO","sparse": false,"Id": "25","SyncToken": "0","MetaData": {"CreateTime": "2018-04-04T12:36:47-07:00","LastUpdatedTime": "2018-04-04T12:36:47-07:00"},"DisplayName": "daraul","PrintOnCheckName": "daraul","Active": true},"time": "2018-04-04T12:36:47.576-07:00"}';

    $mock = new MockHandler([
        new Response(200, [], $vendorResponse),
    ]);

    $handler = HandlerStack::create($mock);
    $client = new Client(['handler' => $handler]);
    $api = new QuickbooksSDK(
        $client,
        'test',
        'test',
        'test',
        'test'
    );

    $this->app->instance(QuickbooksSDK::class, $api);

Now I can run my tests normally, without worrying about third parties. These links were really helpful for me:

http://docs.guzzlephp.org/en/stable/testing.html

https://laravel.com/docs/5.5/container

Let me know if this was helpful.

Sign up to request clarification or add additional context in comments.

2 Comments

Sorry for such the long delay. But if I'm reading this correctly, when I attempt to Mock the class, Mockery isn't Mocking every instance of SingleSignOnApi, and thats why I should use a service provider to ensure that the same instance is always returned in my tests/classes/etc... I don't have access to the example code base anymore, but I will definitely use this the next time I write some unit tests like above. I'll come back and accept the answer if it works for me, thanks a lot, and sorry (again) for the delay... --Cheers
That's fine. I've since changed how I implement my quickbooks tests, but I figure the answer might still be relevant to the question given when it was asked.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.