Moose 类的依赖注入

9

我有一个Moose类,需要发送类型为Foo::Request的请求。我需要使这个依赖在外部可访问,这样我就可以方便地在测试中交换请求实现。我想出了以下属性:

has request_builder => (
    is => 'rw',
    isa => 'CodeRef',
    default => sub {
        sub { Foo::Request->new(@_) }
    }
);

接下来是代码:

my $self = shift;
my $request = $self->request_builder->(path => …);

在测试中:

my $tested_class = …;
my $request = Test::MockObject->new;
$request->mock(…);
$tested_class->request_builder(sub { $request });

有更简单/更符合习惯的解决方案吗?

2
这并不回答你的问题,但你可能会对chromatic的短博客文章感兴趣,其中讨论了相同的技术:您已经在使用依赖注入 - Mike
2
我喜欢这个解决方案,这就是我会解决它的方式。Coderef让你在对象构造方面有很大的灵活性。 - bvr
1
@Mike:感谢你提供的链接。我之前读过这篇文章,在发布这个问题之前又重新阅读了一遍。这是一篇不错的文章,但据我所知它并没有涉及构建器。它只适用于“一次性”依赖项,而不是您的类需要动态构建的依赖项。 - zoul
4个回答

2
使用Moose::Util::apply_all_roles在测试中动态应用角色如何?我一直想使用它,但一直没有找到合适的机会。以下是我认为它的工作原理:
首先,稍微修改您的原始属性:
package MyClientThing;
has request => (
    is      => 'rw',
    isa     => 'Foo::Request',
    builder => '_build_request',
);
sub _build_request { Foo::Request->new };
....

然后创建一个Test::RequestBuilder角色:
package Test::RequestBuilder;
use Moose::Role;
use Test::Foo::Request; # this module could inherit from Foo::Request I guess?
sub _build_request { return Test::Foo::Request->new }; 

同时在 't/my_client_thing.t' 文件中,您可以编写类似以下内容的代码:

use MyClientThing;
use Moose::Util qw( apply_all_roles );
use Test::More;

my $client  = MyClientThing->new;
apply_all_roles( $client, 'Test::RequestBuilder' );  

isa_ok $client->request, 'Test::Foo::Request';

查看Moose::Manual::Roles获取更多信息。


2

我建议按照chromatic的文章模式(Mike在上面评论中提到),进行如下操作:

在你的类中:

has request => (
    is => 'ro',
    isa => 'CodeRef',
    default => sub {
        Foo::Request->new(@_)
    }
);

在你的测试中:

my $request = Test::MockObject->new;
$request->mock(…);
my $tested_class = MyClass->new(request => $request, ...);

该代码与您的代码执行相同的操作,但有以下改进:

  1. 尽可能使属性只读并在构造函数中设置,以获得更好的封装。
  2. 您的request属性是一个可直接使用的对象;无需引用子引用。

1
实际上,如果你必须在类实现中使用构建器,那么你的例子就是正确的。 - dmaestro12
1
谢谢。是的,我需要一个构建器,所以我不能使用在chromatic的博客文章中介绍的简单对象设置器。看起来coderef构建器是最好的解决方案。 - zoul

1
考虑以下方法:
在你的Moose类中定义一个名为make_request的“抽象”方法。然后定义两个角色来实现make_request - 一个调用Foo::Request->new,另一个调用Test::MockObject->new
例如:
你的主类和这两个角色:
package MainMooseClass;
use Moose;
...
# Note: this class requires a role that
# provides an implementation of 'make_request'


package MakeRequestWithFoo;
use Moose::Role;
use Foo::Request; # or require it
sub make_request { Foo::Request->new(...) }

package MakeRequestWithMock;
use Moose::Role;
use Test::MockRequest;  # or require it
sub make_request { Test::MockRequest->new(...) }

如果你想测试你的主类,可以将它与'MakeRequestWithMock'角色混合使用:

package TestVersionOfMainMooseClass;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithMock';

package main;
my $test_object = TestVersionOfMainMooseClass->new(...);

如果您想将其与“make_request”的Foo实现一起使用,请将其与“MakeRequestWithFoo”角色混合在一起。

一些优点:

您只会加载您需要的模块。例如,类TestVersionOfMainMooseClass将不会加载模块Foo::Request

您可以将与make_request实现相关/必需的数据作为新类的实例成员添加。例如,您原来使用CODEREF的方法可以使用此角色实现:

package MakeRequestWithCodeRef;
use Moose::Role;
has request_builder => (
  is => 'rw',
  isa => 'CodeRef',
  required => 1,
);
sub make_request { my $self = shift; $self->request_builder->(@_) };

使用这个类需要为request_builder提供一个初始化器,例如:
package Example;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithCodeRef';

package main;
my $object = Example->new(request_builder => sub { ... });

作为最后的考虑,你编写的角色可能可用于其他类。

0

我知道这篇文章有点旧了,但是对于现在参考这个问题的人来说,请求者可以使用像Bread::Board这样的框架。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接