简体   繁体   English

phpunit测试中的流明IoC绑定分辨率参差不齐

[英]Lumen IoC binding resolution spotty within phpunit tests

I've run into an issue with Lumen v5.0.10 that has me at my wits end. 我遇到了Lumen v5.0.10的问题,这使我不知所措。 I'm designing an application largely using TDD with the bundled phpunit extensions. 我设计的应用程序主要使用带有捆绑的phpunit扩展名的TDD。 I'm basically getting a BindingResolutionException for "App\\Contracts\\SubscriberInteractionInterface". 我基本上得到“ App \\ Contracts \\ SubscriberInteractionInterface”的BindingResolutionException This is an interface in the directory App\\Contracts which has an implementation in App\\Services which is registered in the AppServiceProvider like so: 这是目录App \\ Contracts中的一个接口,在App \\ Services中具有一个实现,该实现已在AppServiceProvider注册,如下所示:

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {

        // Owner manager
        $this->app->singleton(
            'App\Contracts\OwnerInteractionInterface',
            'App\Services\OwnerManager'
        );

        // Subscriber manager
        $this->app->singleton(
            'App\Contracts\SubscriberInteractionInterface', 
            'App\Services\SubscriberManager'
        );

//      dd($this->app->bound('App\Contracts\SubscriberInteractionInterface'));
    }
}

My frustration is that if I uncomment that last line in the function then it shows that App\\Contracts\\SubscriberInteractionInterface has been bound (and thus may be resolved). 我的沮丧之处在于,如果我取消注释函数的最后一行,则表明App\\Contracts\\SubscriberInteractionInterface已绑定(因此可以解决)。

I then have a controller which effectively looks like this 然后我有一个控制器,它看起来像这样

class MyController extends Controller {

    public function __construct(LoggerInterface $log)
    {
        $this->log = $log;
    } 


    public function index(Request $request)
    {
           if (/* Seems to come from owner */)
           {
               $owners = App::make('App\Contracts\OwnerInteractionInterface');
               return $owners->process($request);
           }

           if (/* Seems to come from subscriber */)
           {
               $subscribers = App::make('App\Contracts\SubscriberInteractionInterface');
               return $subscribers->process($request);
           }
    }
}

I use them in this way because I only want the relevant one instantiated (not both as would happen if I type-hinted them) and they also each have type hinted dependencies in their constructors. 我以这种方式使用它们,因为我只希望实例化相关的实例(而不是像我键入它们时那样),并且它们每个都在其构造函数中具有类型提示的依赖关系。

The issue is that the route of the tests that needs OwnerInteractionInterface runs just fine but the one that needs SubscriberInteractionInterface does not. 问题在于,需要OwnerInteractionInterface的测试路由运行得很好,而需要SubscriberInteractionInterface的测试路由OwnerInteractionInterface运行OwnerInteractionInterface The implementations and interfaces are largely similar and as I showed before, they are both registered at the same time and I can confirm that SubscriberInteractionInterface is bound. 这些实现和接口在很大程度上相似,并且正如我之前所展示的,它们都是在同一时间注册的,我可以确认SubscriberInteractionInterface已绑定。 In fact, if I put the line: 事实上,如果我把这一行:

dd(App::bound('App\Contracts\SubscriberInteractionInterface'));

at the top of index() it returns true . index()的顶部,它返回true The tests happen to be ordered such that the path that uses OwnerInteractionInterface runs first and it resolves fine and then the other test fails with a BindingResolutionException . 测试恰好是按顺序进行的,以便使用OwnerInteractionInterface的路径OwnerInteractionInterface运行,并且解析得很好,然后另一个测试失败,并出现BindingResolutionException However, if I omit other tests and run just that one, then everything goes smoothly. 但是,如果我省略其他测试并仅运行该测试,那么一切都会顺利进行。 The tests are in different files and the only setup I do is to bind a mock for a third party API in place of an entirely different binding from those shown and none of that code touches these classes. 测试位于不同的文件中,我唯一要做的设置是绑定第三方API的模拟,以替代与所示的完全不同的绑定,并且这些代码都没有涉及这些类。 This is done within a setUp() function and I make sure to call parent::setUp() within it. 这是在setUp()函数中完成的,我确保在其中调用parent::setUp()

What's going on here? 这里发生了什么? Could it be that binding one concrete instance wipes non-concrete bindings from the IoC ? 绑定一个具体实例是否会擦除IoC非具体绑定? Or is it that the default setup allows some influence to transfer over from one test to another? 还是默认设置允许某些影响从一项测试转移到另一项测试?

I know I sorta have a workaround but the constraint of never running the full test-suite is annoying. 我知道我有一个解决方法,但是从不运行完整的测试套件的约束令人讨厌。 Its starting to seem that testing would be easier if I just use the instance directly instead of resolving it from its interface. 它开始似乎表明,如果我直接使用实例而不是从其接口解析它,则测试会更容易。

Also, does anyone know a way to inspect the IoC for resolvable bindings? 另外,有人知道检查IoC的可解析绑定的方法吗?

After further attempts at debugging, I've found that if you use app(...) in place of App::make(...) then the issue does not come up. 经过进一步的调试尝试,我发现如果您使用app(...)代替App::make(...)则不会出现此问题。 I put in a eval(\\Psy\\sh()) call in the tearDown of the TestCase class and found that after a few tests you get the following result: 我在TestCase类的tearDown中放入eval(\\Psy\\sh())调用,发现在进行几次测试后,您会得到以下结果:

>>> app()->bound('App\Contracts\OwnerInteractionInterface')
=> true
>>> App::bound('App\Contracts\OwnerInteractionInterface')
=> false
>>> App::getFacadeRoot() == app()           
=> false 

This is to say that somehow, the Laravel\\Lumen\\Application instance that the App facade uses to resolve your objects is not the same as the current instance that is created by the setUp() method. 这是说,不知何故, Laravel\\Lumen\\Application的实例App门面用来解决你的对象是一样的是由当前创建的实例setUp()方法。 I think that this instance is the old one from which all bindings have been cleared by a $this->app->flush() call in the tearDown() method so that it can't resolve any custom bindings in any tests that follow the first tearDown() call. 我认为该实例是旧实例,在tearDown()方法中通过$this->app->flush()调用已清除了所有绑定,因此它无法在随后的任何测试中解析任何自定义绑定第一个tearDown()调用。

I've tried to hunt down the issue but for now I have to conclude this project with this workaround. 我试图找出问题所在,但现在我必须使用此变通办法来结束该项目。 I'll update this answer should I find the actual cause. 如果找到实际原因,我将更新此答案。

Instead of use bind , you can use bindIf method. 除了使用bind ,还可以使用bindIf方法。 Container will check whether the abstract has been bound or not. 容器将检查摘要是否已绑定。 If not, it will bind your abstract and vice versa. 如果没有,它将绑定您的摘要,反之亦然。 You can read the api here . 您可以在此处阅读api。

So if you use singleton , you may use bindIf like. 因此,如果您使用singleton ,则可以使用bindIf类的。

// Owner manager
$this->app->bindIf(
    'App\Contracts\OwnerInteractionInterface',
    'App\Services\OwnerManager',
    true
);

// Subscriber manager
$this->app->bindIf(
    'App\Contracts\SubscriberInteractionInterface', 
    'App\Services\SubscriberManager',
    true
);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM