line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MooseX::Test::Role; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $VERSION = '0.06'; |
4
|
|
|
|
|
|
|
|
5
|
6
|
|
|
6
|
|
308364
|
use strict; |
|
6
|
|
|
|
|
16
|
|
|
6
|
|
|
|
|
224
|
|
6
|
6
|
|
|
6
|
|
34
|
use warnings; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
187
|
|
7
|
|
|
|
|
|
|
|
8
|
6
|
|
|
6
|
|
44
|
use Carp qw( confess ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
564
|
|
9
|
6
|
|
|
6
|
|
2416
|
use Class::Load qw( try_load_class ); |
|
6
|
|
|
|
|
93566
|
|
|
6
|
|
|
|
|
303
|
|
10
|
6
|
|
|
6
|
|
38
|
use List::Util qw( first ); |
|
6
|
|
|
|
|
13
|
|
|
6
|
|
|
|
|
673
|
|
11
|
6
|
|
|
6
|
|
34
|
use Test::Builder; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
172
|
|
12
|
|
|
|
|
|
|
|
13
|
6
|
|
|
6
|
|
36
|
use Exporter qw( import unimport ); |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
5723
|
|
14
|
|
|
|
|
|
|
our @EXPORT = qw( requires_ok consumer_of consuming_object consuming_class ); |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
sub requires_ok { |
17
|
1
|
|
|
1
|
1
|
34749
|
my ( $role, @required ) = @_; |
18
|
1
|
|
|
|
|
5
|
my $msg = "$role requires " . join( ', ', @required ); |
19
|
|
|
|
|
|
|
|
20
|
1
|
|
|
|
|
4
|
my $role_type = _derive_role_type($role); |
21
|
0
|
0
|
|
|
|
0
|
if (!$role_type) { |
22
|
0
|
|
|
|
|
0
|
ok( 0, $msg ); |
23
|
0
|
|
|
|
|
0
|
return; |
24
|
|
|
|
|
|
|
} |
25
|
|
|
|
|
|
|
|
26
|
0
|
|
|
|
|
0
|
foreach my $req (@required) { |
27
|
0
|
0
|
|
0
|
|
0
|
unless ( first { $_ eq $req } _required_methods($role_type, $role) ) { |
|
0
|
|
|
|
|
0
|
|
28
|
0
|
|
|
|
|
0
|
ok( 0, $msg ); |
29
|
0
|
|
|
|
|
0
|
return; |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
} |
32
|
0
|
|
|
|
|
0
|
ok( 1, $msg ); |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub consuming_class { |
36
|
2
|
|
|
2
|
1
|
28537
|
my ( $role, %args ) = @_; |
37
|
|
|
|
|
|
|
|
38
|
2
|
50
|
|
|
|
12
|
my %methods = exists $args{methods} ? %{ $args{methods} } : (); |
|
0
|
|
|
|
|
0
|
|
39
|
|
|
|
|
|
|
|
40
|
2
|
|
|
|
|
11
|
my $role_type = _derive_role_type($role); |
41
|
0
|
0
|
|
|
|
0
|
confess 'first argument should be a role' unless $role_type; |
42
|
|
|
|
|
|
|
|
43
|
0
|
|
|
|
|
0
|
my $package = _package_name(); |
44
|
0
|
|
|
|
|
0
|
_add_methods( |
45
|
|
|
|
|
|
|
package => $package, |
46
|
|
|
|
|
|
|
role_type => $role_type, |
47
|
|
|
|
|
|
|
role => $role, |
48
|
|
|
|
|
|
|
methods => \%methods, |
49
|
|
|
|
|
|
|
); |
50
|
|
|
|
|
|
|
|
51
|
0
|
|
|
|
|
0
|
_apply_role( |
52
|
|
|
|
|
|
|
package => $package, |
53
|
|
|
|
|
|
|
role_type => $role_type, |
54
|
|
|
|
|
|
|
role => $role, |
55
|
|
|
|
|
|
|
); |
56
|
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
0
|
return $package; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub consuming_object { |
61
|
1
|
|
|
1
|
1
|
28794
|
my $class = consuming_class(@_); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# Moose and Moo can be instantiated and should be. Role::Tiny however isn't |
64
|
|
|
|
|
|
|
# a full OO implementation and so doesn't provide a "new" method. |
65
|
0
|
0
|
|
|
|
0
|
return $class->can('new') ? $class->new() : $class; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub consumer_of { |
69
|
1
|
|
|
1
|
1
|
27597
|
my ( $role, %methods ) = @_; |
70
|
|
|
|
|
|
|
|
71
|
1
|
0
|
|
|
|
5
|
confess 'first argument to consumer_of should be a role' unless _derive_role_type($role); |
72
|
|
|
|
|
|
|
|
73
|
0
|
|
|
|
|
0
|
return consuming_object( $role, methods => \%methods ); |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub _required_methods { |
77
|
0
|
|
|
0
|
|
0
|
my ($role_type, $role) = @_; |
78
|
0
|
|
|
|
|
0
|
my @methods; |
79
|
|
|
|
|
|
|
|
80
|
0
|
0
|
|
|
|
0
|
if ($role_type eq 'Moose::Role') { |
|
|
0
|
|
|
|
|
|
81
|
0
|
|
|
|
|
0
|
@methods = $role->meta->get_required_method_list(); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
elsif ($role_type eq 'Role::Tiny') { |
84
|
0
|
|
|
|
|
0
|
my $info = _role_tiny_info($role); |
85
|
0
|
0
|
0
|
|
|
0
|
if ($info && ref($info->{requires}) eq 'ARRAY') { |
86
|
0
|
|
|
|
|
0
|
@methods = @{$info->{requires}}; |
|
0
|
|
|
|
|
0
|
|
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
0
|
0
|
|
|
|
0
|
return wantarray ? @methods : \@methods; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub _derive_role_type { |
94
|
4
|
|
|
4
|
|
9
|
my $role = shift; |
95
|
|
|
|
|
|
|
|
96
|
4
|
0
|
33
|
|
|
71
|
if ($role->can('meta') && $role->meta()->isa('Moose::Meta::Role')) { |
97
|
|
|
|
|
|
|
# Also covers newer Moo::Roles |
98
|
0
|
|
|
|
|
|
return 'Moose::Role'; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
0
|
0
|
0
|
|
|
|
if (try_load_class('Role::Tiny') && _role_tiny_info($role)) { |
102
|
|
|
|
|
|
|
# Also covers older Moo::Roles |
103
|
0
|
|
|
|
|
|
return 'Role::Tiny'; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
0
|
|
|
|
|
|
return; |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
my $package_counter = 0; |
110
|
|
|
|
|
|
|
sub _package_name { |
111
|
0
|
|
|
0
|
|
|
return 'MooseX::Test::Role::Consumer' . $package_counter++; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub _apply_role { |
115
|
0
|
|
|
0
|
|
|
my %args = @_; |
116
|
|
|
|
|
|
|
|
117
|
0
|
|
|
|
|
|
my $package = $args{package}; |
118
|
0
|
|
|
|
|
|
my $role_type = $args{role_type}; |
119
|
0
|
|
|
|
|
|
my $role = $args{role}; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# We'll need a thing that exports a "with" sub |
122
|
0
|
|
|
|
|
|
my $with_exporter; |
123
|
0
|
0
|
|
|
|
|
if ($role_type eq 'Moose::Role') { |
|
|
0
|
|
|
|
|
|
124
|
0
|
|
|
|
|
|
$with_exporter = 'Moose'; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
elsif ($role_type eq 'Role::Tiny') { |
127
|
0
|
|
|
|
|
|
$with_exporter = 'Role::Tiny::With'; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
else { |
130
|
0
|
|
|
|
|
|
confess "Unknown role type $role_type"; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
|
my $source = qq{ |
134
|
|
|
|
|
|
|
package $package; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
use $with_exporter; |
137
|
|
|
|
|
|
|
with('$role'); |
138
|
|
|
|
|
|
|
}; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
#warn $source; |
141
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
|
eval($source); |
143
|
0
|
0
|
|
|
|
|
die $@ if $@; |
144
|
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
return $package; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub _add_methods { |
149
|
0
|
|
|
0
|
|
|
my %args = @_; |
150
|
|
|
|
|
|
|
|
151
|
0
|
|
|
|
|
|
my $role_type = $args{role_type}; |
152
|
0
|
|
|
|
|
|
my $package = $args{package}; |
153
|
0
|
|
|
|
|
|
my $role = $args{role}; |
154
|
0
|
|
|
|
|
|
my $methods = $args{methods}; |
155
|
|
|
|
|
|
|
|
156
|
0
|
|
0
|
0
|
|
|
$methods->{$_} ||= sub { undef } for _required_methods( $role_type, $role ); |
|
0
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
|
158
|
0
|
|
|
|
|
|
my $meta; |
159
|
0
|
0
|
|
|
|
|
$meta = Moose::Meta::Class->create($package) if $role_type eq 'Moose::Role'; |
160
|
|
|
|
|
|
|
|
161
|
0
|
|
|
|
|
|
while ( my ( $method, $subref ) = each(%{$methods}) ) { |
|
0
|
|
|
|
|
|
|
162
|
0
|
0
|
|
|
|
|
if ($meta) { |
163
|
0
|
|
|
|
|
|
$meta->add_method($method => $subref); |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
else { |
166
|
6
|
|
|
6
|
|
36
|
no strict 'refs'; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
1325
|
|
167
|
|
|
|
|
|
|
#no warnings 'redefine'; |
168
|
0
|
|
|
|
|
|
*{ $package . '::' . $method } = $subref; |
|
0
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
0
|
|
|
|
|
|
return; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub _role_tiny_info { |
176
|
|
|
|
|
|
|
# This seems brittle, but there aren't many options to get this data. |
177
|
|
|
|
|
|
|
# Moo relies on %INFO too, so it seems like it would be a hard thing |
178
|
|
|
|
|
|
|
# for to move away from. |
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
0
|
|
|
my $role = shift; |
181
|
0
|
|
|
|
|
|
return $Role::Tiny::INFO{$role}; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
my $Test = Test::Builder->new(); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# Done this way for easier testing |
187
|
|
|
|
|
|
|
our $ok = sub { $Test->ok(@_) }; |
188
|
0
|
|
|
0
|
0
|
|
sub ok { $ok->(@_) } |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
1; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=pod |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 NAME |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
MooseX::Test::Role - Test functions for Moose roles |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head1 SYNOPSIS |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
use MooseX::Test::Role; |
201
|
|
|
|
|
|
|
use Test::More tests => 2; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
requires_ok('MyRole', qw/method1 method2/); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
my $consumer = consuming_object( |
206
|
|
|
|
|
|
|
'MyRole', |
207
|
|
|
|
|
|
|
methods => { |
208
|
|
|
|
|
|
|
method1 => sub { 1 } |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
); |
211
|
|
|
|
|
|
|
ok( $consumer->myrole_method ); |
212
|
|
|
|
|
|
|
is( $consumer->method1, 1 ); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
my $consuming_class = consuming_class('MyRole'); |
215
|
|
|
|
|
|
|
ok( $consuming_class->class_method() ); |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=head1 DESCRIPTION |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
Provides functions for testing roles. Supports roles created with |
220
|
|
|
|
|
|
|
L, L or L. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 BACKGROUND |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Unit testing a role can be hard. A major problem is creating classes that |
225
|
|
|
|
|
|
|
consume the role. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
One could side-step the problem entirely and just call the subroutines in the |
228
|
|
|
|
|
|
|
role's package directly. For example, |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Fooable->bar(); |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
That only works until C calls another method in the consuming class |
233
|
|
|
|
|
|
|
though. Mock objects are a tempting way to solve that problem: |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
my $consumer = Test::MockObject->new(); |
236
|
|
|
|
|
|
|
$consumer->set_always('baz', 1); |
237
|
|
|
|
|
|
|
Fooable::bar($consumer); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
But if C happens to call another method in the role then |
240
|
|
|
|
|
|
|
the mock consumer will have to mock that method too. |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
A better way is to create a class to consume the role: |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
package FooableTest; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
use Moose; |
247
|
|
|
|
|
|
|
with 'Fooable'; |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub required_method {} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
package main; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
my $consumer = FooableTest->new(); |
254
|
|
|
|
|
|
|
$consumer->bar(); |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
This can work well for some roles. Unfortunately, if several variations have to |
257
|
|
|
|
|
|
|
be tested, it may be necessary to create several consuming test classes, which |
258
|
|
|
|
|
|
|
gets tedious. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Moose can create anonymous classes which consume roles: |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
my $consumer = Moose::Meta::Class->create_anon_class( |
263
|
|
|
|
|
|
|
roles => ['Fooable'], |
264
|
|
|
|
|
|
|
methods => { |
265
|
|
|
|
|
|
|
required_method => sub {}, |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
)->new_object(); |
268
|
|
|
|
|
|
|
$consumer->bar(); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
This can still be tedious, especially for roles that require lots of methods. |
271
|
|
|
|
|
|
|
C simply makes this easier to do. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head1 EXPORTED FUNCTIONS |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=over 4 |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=item C \%methods)> |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Creates a class which consumes the role, and returns it's package name. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
C<$role> must be the package name of a role. L, L and |
282
|
|
|
|
|
|
|
L are supported. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Any method required by the role will be stubbed. To override the default stub |
285
|
|
|
|
|
|
|
methods, or to add additional methods, specify the name and a coderef: |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
consuming_class('MyRole', |
288
|
|
|
|
|
|
|
method1 => sub { 'one' }, |
289
|
|
|
|
|
|
|
method2 => sub { 'two' }, |
290
|
|
|
|
|
|
|
required_method => sub { 'required' }, |
291
|
|
|
|
|
|
|
); |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=item C \%methods)> |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Creates a class which consumes the role, and returns an instance of it. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
If the class does not have a C method (which is commonly the case for |
298
|
|
|
|
|
|
|
L), then the package name will be returned instead. |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
See C for arguments. C is essentially |
301
|
|
|
|
|
|
|
equivalent to: |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
consuming_class(@_)->new(); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=item C |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
Alias of C, without named arguments. This is left in for |
308
|
|
|
|
|
|
|
compatibility, new code should use C. |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=item C |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
Tests if role requires one or more methods. |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=back |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=head1 GITHUB |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
Patches, comments or mean-spirited code reviews are all welcomed on GitHub: |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
L |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=head1 AUTHOR |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Paul Boyd |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
This software is copyright (c) 2014 by Paul Boyd. |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
331
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=cut |