Moose 类的依赖注入

发布于 2024-11-30 12:23:26 字数 591 浏览 1 评论 0原文

我有一个 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 });

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

I have a Moose class that needs to send requests of type Foo::Request. I need to make this dependency accessible from the outside, so that I can easily exchange the request implementation in tests. I came up with the following attribute:

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

And then in code:

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

And in tests:

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

Is there a more simple / more idiomatic solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

故事和酒 2024-12-07 12:23:26

如何使用 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 了解更多信息。

How about applying a role dynamically in your tests with Moose::Util::apply_all_roles? I have been wanting to use this for a while, but haven't had an excuse yet. Here is how I think it would work.

First, modify your original attribute slightly:

package MyClientThing;
has request => (
    is      => 'rw',
    isa     => 'Foo::Request',
    builder => '_build_request',
);
sub _build_request { Foo::Request->new };
....

Then create a Test::RequestBuilder role:

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

Meanwhile in 't/my_client_thing.t' you would write something like this:

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

See Moose::Manual::Roles for more info.

傲鸠 2024-12-07 12:23:26

我的建议是,按照 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 属性是一个随时可用的对象;无需取消引用子引用

My suggestion, following the model in chromatic's article (comment above by Mike), is this:

In your class:

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

In your test:

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

Does exactly what your code does, with the following refinements:

  1. make the attribute read-only and set it in the constructor, if possible, for better encapsulation.
  2. your request attribute is a ready-to-use object; no need to dereference the sub ref
︶葆Ⅱㄣ 2024-12-07 12:23:26

考虑这种方法:

在您的 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 { ... });

作为最终考虑,您编写的角色可能是可与其他类一起使用。

Consider this approach:

In your Moose class define an 'abstract' method called make_request. Then define two roles which implement make_request - one which calls Foo::Request->new and another one which calls Test::MockObject->new.

Example:

Your main class and the two roles:

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(...) }

If you want to test your main class, mix it with the 'MakeRequestWithMock' role:

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

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

If you want to use it with the Foo implementation of 'make_request', mix it in with the 'MakeRequestWithFoo' role.

Some advantages:

You will only load in modules that you need. For instance, the class TestVersionOfMainMooseClass will not load the module Foo::Request.

You can add data that is relevant/required by your implementation of make_request as instance members of your new class. For example, your original approach of using a CODEREF can be implemented with this role:

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

To use this class you need to supply an initializer for request_builder, e.g.:

package Example;
use Moose;
extends 'MainMooseClass';
with 'MakeRequestWithCodeRef';

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

As a final consideration, the roles you write might be usable with other classes.

¢蛋碎的人ぎ生 2024-12-07 12:23:26

我知道这篇文章有点旧,但对于任何提到这个问题的人来说,现在请求者可以使用像 这样的框架面包::板

I know this post is a little old, but for anyone referring to this question now the requester could use a framework like Bread::Board.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文