Moose类的依赖注入

时间:2011-08-19 07:38:35

标签: perl dependency-injection moose

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

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

是否有更简单/更惯用的解决方案?

4 个答案:

答案 0 :(得分: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

答案 1 :(得分: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属性是一个随时可用的对象;无需取消引用sub ref

答案 2 :(得分:1)

考虑这种方法:

在你的Moose类中定义一个名为make_request的'abstract'方法。然后定义两个实现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 { ... });

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

答案 3 :(得分:0)

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