嘲弄,App Instance 在某些 laravel 测试中不起作用

Posted

技术标签:

【中文标题】嘲弄,App Instance 在某些 laravel 测试中不起作用【英文标题】:Mockery, App Instance not working on some laravel tests 【发布时间】:2018-08-14 08:44:06 【问题描述】:

基本上,我的问题是;为什么$this->app->instance() 调用对模拟对象的一个​​实例起作用,而另一个不起作用...

在下面的示例中,实际上调用了 getGroupSingleSignOnLink 函数,另一个被模拟并且测试通过了...

测试

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


控制器功能 - 测试模拟工作

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

控制器功能 - 测试模拟不起作用

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

【问题讨论】:

什么输出让你相信问题出在instance 输出...我知道因为该类在失败的测试中没有被模拟,所以它进行了外部调用...但是另一个测试,同一个类被模拟得很好。跨度> 你有没有设法让它工作?我正在尝试使用相同的方法模拟一些外部 API 调用,但它不起作用。 不,对不起@daraul...我们永远无法弄清楚发生了什么。 我实际上能够通过一些更改使其始终如一地工作。基本上,我实例化了我在 AppServiceProvider 中使用的第三方 SDK。然后在我的测试中,我构建了一个带有虚假响应的模拟版本,并将其传递给$this->app->instance()。等我再等几分钟后,我会写一个更长的帖子作为答案 【参考方案1】:

我将使用 Quickbooks 作为示例来说明我如何让它始终如一地为我工作。

这是我的 AppServiceProvider,定义了我创建的自定义 QuickbooksSDK 类:

...
    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')
            );
        );
    

然后我创建了第二个自定义类,称为 QuickbooksInvoicingDriver,它从服务容器中获取实例化的 SDK 类:

public function __construct()

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

最后,在我的测试类中,我可以使用我自己的自定义响应来模拟 QuickbooksSDK,以简化测试:

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

现在我可以正常运行测试,无需担心第三方。这些链接对我真的很有帮助:

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

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

如果这有帮助,请告诉我。

【讨论】:

抱歉,耽搁了这么久。但是如果我没看错的话,当我尝试模拟类时,Mockery 不会模拟 SingleSignOnApi 的每个实例,这就是为什么我应该使用服务提供者来确保在我的测试/类中总是返回相同的实例/etc... 我无法再访问示例代码库了,但是下次我写一些像上面这样的单元测试时我肯定会使用它。如果它对我有用,我会回来接受答案,非常感谢,并且(再次)抱歉延迟...... --Cheers 没关系。从那以后,我改变了实施快速书测试的方式,但我认为答案可能仍然与被问到的问题相关。

以上是关于嘲弄,App Instance 在某些 laravel 测试中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

laraval开发之QQ登录

laraver框架学习------工厂模型填充测试数据

用嘲弄来嘲弄 trait 方法

嘲弄如何断言类实例

Laravel 的嘲弄不会模拟方法

嘲弄重载类缺少方法