File Coverage

blib/lib/Test/Mock/Class/Role/Object.pm
Criterion Covered Total %
statement 152 159 95.6
branch 71 94 75.5
condition 33 59 55.9
subroutine 28 28 100.0
pod 14 14 100.0
total 298 354 84.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -c
2              
3             package Test::Mock::Class::Role::Object;
4              
5             =head1 NAME
6              
7             Test::Mock::Class::Role::Object - Role for base object of mock class
8              
9             =head1 DESCRIPTION
10              
11             This role provides an API for defining and changing behavior of mock class.
12              
13             =cut
14              
15 1     1   1035 use 5.006;
  1         4  
  1         41  
16              
17 1     1   6 use strict;
  1         2  
  1         37  
18 1     1   5 use warnings;
  1         1  
  1         59  
19              
20             our $VERSION = '0.0303';
21              
22 1     1   13 use Moose::Role;
  1         3  
  1         11  
23              
24              
25 1     1   3745 use Symbol ();
  1         2  
  1         22  
26              
27 1     1   6 use Test::Assert ':all';
  1         2  
  1         10  
28              
29              
30             ## no critic qw(ProhibitConstantPragma)
31 1     1   6737 use constant Exception => 'Test::Mock::Class::Exception';
  1         3  
  1         148  
32 1     1   1115 use English '-no_match_vars';
  1         2463  
  1         7  
33              
34             use Exception::Base (
35 1         12 Exception,
36             'Exception::Fatal',
37             '+ignore_package' => [__PACKAGE__],
38 1     1   575 );
  1         2  
39              
40              
41             =head1 ATTRIBUTES
42              
43             =over
44              
45             =item B<_mock_call> : HashRef
46              
47             Count of method calls stored as HashRef.
48              
49             =cut
50              
51             has '_mock_call' => (
52             is => 'ro',
53             isa => 'HashRef',
54             default => sub { {} },
55             );
56              
57              
58             =item B<_mock_expectation> : HashRef
59              
60             Expectations for mock methods stored as HashRef.
61              
62             =cut
63              
64             has '_mock_expectation' => (
65             is => 'ro',
66             isa => 'HashRef',
67             default => sub { {} },
68             );
69              
70              
71             =item B<_mock_action> : HashRef
72              
73             Return values or actions for mock methods stored as HashRef.
74              
75             =back
76              
77             =cut
78              
79             has '_mock_action' => (
80             is => 'ro',
81             isa => 'HashRef',
82             default => sub { {} },
83             );
84              
85              
86 1     1   6084 use namespace::clean -except => 'meta';
  1         2  
  1         12  
87              
88              
89             ## no critic qw(RequireCheckingReturnValueOfEval)
90              
91             =head1 METHODS
92              
93             =over
94              
95             =item B<mock_tally>(I<>) : Self
96              
97             Check the expectations at the end. It should be called expicitly if
98             C<minimum> or C<count> parameter was used for expectation, or following
99             methods was called: C<mock_expect_at_least_once>,
100             C<mock_add_expectation_call_count>, C<mock_expect_minimum_call_count>
101             or C<mock_expect_once>.
102              
103             =cut
104              
105             sub mock_tally {
106 40     40 1 2451 my ($self) = @_;
107              
108 40         2006 my $expectation = $self->_mock_expectation;
109              
110 40 50 50     375 return if not defined $expectation
      33        
111             or (ref $expectation || '') ne 'HASH';
112              
113 40         56 foreach my $method (keys %{ $expectation }) {
  40         156  
114 26 50 50     232 next if not defined $expectation->{$method}
      33        
115             or (ref $expectation->{$method} || '') ne 'ARRAY';
116              
117 26         43 foreach my $rule (@{ $expectation->{$method} }) {
  26         74  
118 29 100       105 if (defined $rule->{count}) {
119 7   100     26 my $count = $rule->{call} || 0;
120 7 100       40 fail([
121             'Expected call count (%d) for method (%s) with calls (%d)',
122             $rule->{count}, $method, $count
123             ]) if ($count != $rule->{count});
124             };
125 28 100       155 if (defined $rule->{minimum}) {
126 6   100     27 my $count = $rule->{call} || 0;
127 6 100       40 fail([
128             'Minimum call count (%d) for method (%s) with calls (%d)',
129             $rule->{minimum}, $method, $count
130             ]) if ($count < $rule->{minimum});
131             };
132             };
133             };
134              
135 38         190 return $self;
136             };
137              
138              
139             =item B<mock_invoke>( I<method> : Str, I<args> : Array ) : Any
140              
141             Increases the call counter and returns the expected value for the method name
142             and checks expectations. Will generate any test assertions as a result of
143             expectations if there is a test present.
144              
145             If more that one expectation matches, all of them are checked. If one of them
146             fails, the whole C<mock_invoke> method is failed.
147              
148             This method is called in overridden methods of mock class, but you need to
149             call it explicitly if you constructed own method.
150              
151             =cut
152              
153             sub mock_invoke {
154 145     145 1 344 my ($self, $method, @args) = @_;
155              
156 145         186 assert_not_null($method) if ASSERT;
157              
158 145         450 my $timing = $self->_mock_add_call($method, @args);
159 145         485 $self->_mock_check_expectations($method, $timing, @args);
160 139         527 return $self->_mock_emulate_call($method, $timing, @args);
161             };
162              
163              
164             =item B<mock_return>( I<method> : Str, I<value> : Any, :I<at> : Int, :I<args> : ArrayRef[Any] ) : Self
165              
166             Sets a return for a parameter list that will be passed on by call to this
167             method that match.
168              
169             The first value is returned if more than one parameter list matches method's
170             arguments. The C<undef> value is returned if none of parameters matches.
171              
172             =over
173              
174             =item method
175              
176             Method name.
177              
178             =item value
179              
180             Returned value.
181              
182             $m->mock_return( 'open', 1 );
183              
184             If value is coderef, then it is called with method name, current timing
185             and original arguments as arguments. It allows to return array rather than
186             scalar.
187              
188             $m->mock_return( 'sequence', sub {
189             qw( one two three )[ $_[1] ]
190             } );
191             $m->mock_return( 'get_array', sub { (1,2,3) } );
192              
193             =item at
194              
195             Value is returned only for current timing, started from C<0>.
196              
197             $m->mock_return( 'sequence', 'one', at => 0 );
198             $m->mock_return( 'sequence', 'two', at => 1 );
199             $m->mock_return( 'sequence', 'three', at => 2 );
200              
201             =item args
202              
203             Value is returned only if method is called with proper argument.
204              
205             $m->mock_return( 'get_value', 'admin', args => ['dbuser'] );
206             $m->mock_return( 'get_value', 'secret', args => ['dbpass'] );
207             $m->mock_return( 'get_value', sub { $_[2] }, args => [qr/.*/] );
208              
209             =back
210              
211             =cut
212              
213             sub mock_return {
214 34     34 1 6199 my ($self, $method, $value, %params) = @_;
215              
216 34 50       92 $self->throw_error(
217             'Usage: $mock->mock_return( METHOD => VALUE, PARAMS )'
218             ) unless defined $method;
219              
220 34         42 assert_equals('HASH', ref $self->_mock_action) if ASSERT;
221 34         51 push @{ $self->_mock_action->{$method} } => { %params, value => $value };
  34         1750  
222              
223 34         119 return $self;
224             };
225              
226              
227             =item B<mock_return_at>( I<at> : Int, I<method> : Str, I<value> : Any, :I<args> : ArrayRef[Any] ) : Self
228              
229             Convenience method for returning a value upon the method call.
230              
231             =cut
232              
233             sub mock_return_at {
234 16     16 1 5093 my ($self, $at, $method, $value, %params) = @_;
235              
236 16 50 33     101 $self->throw_error(
237             message => 'Usage: $mock->mock_return_at( AT, METHOD => VALUE, PARAMS )'
238             ) unless defined $at and defined $method;
239              
240 16         54 return $self->mock_return( $method => $value, %params, at => $at );
241             };
242              
243              
244             =item B<mock_throw>( I<method> : Str, :I<at> : Int, I<exception> : Str|Object, :I<args> : ArrayRef[Any], I<params> : Hash ) : Self
245              
246             Sets up a trigger to throw an exception upon the method call. The method
247             takes the same arguments as C<mock_return>.
248              
249             If an I<exception> parameter is a string, the L<Exception::Assertion> is
250             thrown with this parameter as its message and rest of parameters as its
251             arguments. If an I<exception> parameter is an object reference, the C<throw>
252             method is called on this object with predefined message and rest of parameters
253             as its arguments.
254              
255             =cut
256              
257             sub mock_throw {
258 4     4 1 3245 my ($self, $method, $exception, %params) = @_;
259              
260 4 50       18 Exception::Argument->throw(
261             message => 'Usage: $mock->mock_throw( METHOD => EXCEPTION, PARAMS )'
262             ) unless defined $method;
263              
264 4 100       52 $exception = Exception::Assertion->new(
265             message => $exception,
266             reason => ['Thrown on method (%s)', $method],
267             %params
268             ) unless blessed $exception;
269              
270 4         971 assert_equals('HASH', ref $self->_mock_action) if ASSERT;
271 4         227 push @{ $self->_mock_action->{$method} } => {
272             %params,
273             value => sub {
274 5     5   29 $exception->throw;
275             },
276 4         10 };
277              
278 4         18 return $self;
279             };
280              
281              
282             =item B<mock_throw_at>( I<at> : Int, I<method> : Str, I<exception> : Str|Object, :I<args> : ArrayRef[Any] ) : Self
283              
284             Convenience method for throwing an error upon the method call.
285              
286             =cut
287              
288             sub mock_throw_at {
289 1     1 1 1038 my ($self, $at, $method, $exception, %params) = @_;
290              
291 1 50 33     13 Exception::Argument->throw(
292             message => 'Usage: $mock->mock_throw_at( AT, METHOD => EXCEPTION, PARAMS )'
293             ) unless defined $at and defined $method;
294              
295 1         9 return $self->mock_throw( $method => $exception, %params, at => $at );
296             };
297              
298              
299             =item B<mock_expect>( I<method> : Str, :I<at> : Int, :I<minimum> : Int, :I<maximum> : Int, :I<count> : Int, :I<args> : ArrayRef[Any] ) : Self
300              
301             Sets up an expected call with a set of expected parameters in that call. Each
302             call will be compared to these expectations regardless of when the call is
303             made. The method takes the same arguments as C<mock_return>.
304              
305             =cut
306              
307             sub mock_expect {
308 36     36 1 7487 my ($self, $method, %params) = @_;
309              
310 36 50       139 Exception::Argument->throw(
311             message => 'Usage: $mock->mock_expect( METHOD => PARAMS )'
312             ) unless defined $method;
313              
314 36 100       184 Exception->throw(
315             message => ['Cannot set expected arguments as no method (%s) in class (%s)', $method, $self->meta->name],
316             ) unless $self->meta->has_method($method);
317              
318 35         1933 assert_equals('HASH', ref $self->_mock_expectation) if ASSERT;
319 35         52 push @{ $self->_mock_expectation->{$method} } => {
  35         1714  
320             %params,
321             };
322              
323 35         134 return $self;
324             };
325              
326              
327             =item B<mock_expect_at>( I<at> : Int, I<method> : Str, :I<args> : ArrayRef[Any] ) : Self
328              
329             Sets up an expected call with a set of expected parameters in that call.
330              
331             =cut
332              
333             sub mock_expect_at {
334 4     4 1 1902 my ($self, $at, $method, %params) = @_;
335              
336 4 50 33     28 Exception::Argument->throw(
337             message => 'Usage: $mock->mock_expect_at( AT, METHOD => PARAMS )'
338             ) unless defined $at and defined $method;
339              
340 4         35 return $self->mock_expect( $method => %params, at => $at );
341             };
342              
343              
344             =item B<mock_expect_call_count>( I<method> : Str, I<count> : Int, :I<args> : ArrayRef[Any] ) : Self
345              
346             Sets an expectation for the number of times a method will be called. The
347             C<mock_tally> method have to be used to check this.
348              
349             =cut
350              
351             sub mock_expect_call_count {
352 1     1 1 5 my ($self, $method, $count, %params) = @_;
353              
354 1 50 33     8 Exception::Argument->throw(
355             message => 'Usage: $mock->mock_expect_call_count( METHOD, COUNT => PARAMS )'
356             ) unless defined $method and defined $count;
357              
358 1         5 return $self->mock_expect( $method => %params, count => $count );
359             };
360              
361              
362             =item B<mock_expect_maximum_call_count>( I<method> : Str, I<count> : Int, :I<args> : ArrayRef[Any] ) : Self
363              
364             Sets the number of times a method may be called before a test failure is
365             triggered.
366              
367             =cut
368              
369             sub mock_expect_maximum_call_count {
370 5     5 1 4492 my ($self, $method, $count, %params) = @_;
371              
372 5 50 33     44 Exception::Argument->throw(
373             message => 'Usage: $mock->mock_expect_maximum_call_count( METHOD, COUNT => PARAMS )'
374             ) unless defined $method and defined $count;
375              
376 5         26 return $self->mock_expect( $method => %params, maximum => $count );
377             };
378              
379              
380             =item B<mock_expect_minimum_call_count>( I<method> : Str, I<count> : Int, :I<args> : ArrayRef[Any] ) : Self
381              
382             Sets the number of times to call a method to prevent a failure on the tally.
383              
384             =cut
385              
386             sub mock_expect_minimum_call_count {
387 3     3 1 2680 my ($self, $method, $count, %params) = @_;
388              
389 3 50 33     26 Exception::Argument->throw(
390             message => 'Usage: $mock->mock_expect_minimum_call_count( METHOD, COUNT => PARAMS )'
391             ) unless defined $method and defined $count;
392              
393 3         16 return $self->mock_expect( $method => %params, minimum => $count );
394             };
395              
396              
397             =item B<mock_expect_never>( I<method> : Str, :I<args> : ArrayRef[Any] ) : Self
398              
399             Convenience method for barring a method call.
400              
401             =cut
402              
403             sub mock_expect_never {
404 5     5 1 2388 my ($self, $method, %params) = @_;
405              
406 5 50       18 Exception::Argument->throw(
407             message => 'Usage: $mock->mock_expect_never( METHOD => PARAMS )'
408             ) unless defined $method;
409              
410 5         28 return $self->mock_expect( $method => %params, maximum => 0 );
411             };
412              
413              
414             =item B<mock_expect_once>( I<method> : Str, :I<args> : ArrayRef[Any] ) : Self
415              
416             Convenience method for a single method call.
417              
418             =cut
419              
420             sub mock_expect_once {
421 7     7 1 5955 my ($self, $method, %params) = @_;
422              
423 7 50       27 Exception::Argument->throw(
424             message => 'Usage: $mock->mock_expect_once( METHOD => PARAMS )'
425             ) unless defined $method;
426              
427 7         39 return $self->mock_expect( $method => %params, count => 1 );
428             };
429              
430              
431             =item B<mock_expect_at_least_once>( I<method> : Str, :I<args> : ArrayRef[Any] ) : Self
432              
433             Convenience method for requiring a method call.
434              
435             =cut
436              
437             sub mock_expect_at_least_once {
438 3     3 1 10823 my ($self, $method, %params) = @_;
439              
440 3 50       17 Exception::Argument->throw(
441             message => 'Usage: $mock->mock_expect_at_least_once( METHOD => PARAMS )'
442             ) unless defined $method;
443              
444 3         17 return $self->mock_expect( $method => %params, minimum => 1 );
445             };
446              
447              
448             =item B<_mock_emulate_call>( I<method> : Str, I<timing> : Int, I<args> : Array ) : Any
449              
450             Finds the return value matching the incoming arguments. If there is no
451             matching value found then an error is triggered.
452              
453             =cut
454              
455             sub _mock_emulate_call {
456 139     139   321 my ($self, $method, $timing, @args) = @_;
457              
458 139         236 assert_not_null($method) if ASSERT;
459 139         129 assert_not_null($timing) if ASSERT;
460              
461 139         6361 my $rules_for_method = $self->_mock_action->{$method};
462              
463 139 100 50     882 return if not defined $rules_for_method
      66        
464             or (ref $rules_for_method || '') ne 'ARRAY';
465              
466             RULE:
467 48         93 foreach my $rule (@$rules_for_method) {
468 75 100       253 if (defined $rule->{at}) {
469 42 100       112 next unless $timing == $rule->{at};
470             };
471              
472 48 100       126 if (exists $rule->{args}) {
473 27         80 my @rule_args = (ref $rule->{args} || '') eq 'ARRAY'
474 27 50 50     103 ? @{ $rule->{args} }
475             : ( $rule->{args} );
476              
477             # number of args matches?
478 27 100       92 next unless @args == @rule_args;
479              
480             # iterate args
481 24         72 foreach my $i (0 .. @rule_args - 1) {
482 35         64 my $rule_arg = $rule_args[$i];
483 35 100 100     165 if ((ref $rule_arg || '') eq 'Regexp') {
    50          
484 9 100       66 next RULE unless $args[$i] =~ $rule_arg;
485             }
486             elsif (ref $rule_arg) {
487             # TODO: use Test::Deep::NoTest
488 0         0 eval {
489 0         0 assert_deep_equals($rule_arg, $args[$i]);
490             };
491 0 0       0 next RULE if $EVAL_ERROR;
492             }
493             else {
494             # TODO: do not use eval
495 26         39 eval {
496 26         176 assert_equals($rule_arg, $args[$i]);
497             };
498 26 100       5968 next RULE if $EVAL_ERROR;
499             };
500             };
501             };
502              
503 37 100       171 if (ref $rule->{value} eq 'CODE') {
    100          
504 6         19 return $rule->{value}->(
505             $method, $timing, @args
506             );
507             }
508             elsif (defined $rule->{value}) {
509 29         212 return $rule->{value};
510             };
511             };
512              
513 13         104 return;
514             };
515              
516              
517             =item B<_mock_add_call>( I<method> : Str, I<args> : Array ) : Int
518              
519             Adds one to the call count of a method and returns previous value.
520              
521             =cut
522              
523             sub _mock_add_call {
524 145     145   325 my ($self, $method, @args) = @_;
525              
526 145         202 assert_not_null($method) if ASSERT;
527              
528 145         185 assert_equals('HASH', ref $self->_mock_call) if ASSERT;
529 145         6697 return $self->_mock_call->{$method}++;
530             };
531              
532             =item B<_mock_check_expectations>( I<method> : Str, I<timing> : Num, I<args> : Array ) : Self
533              
534             Tests the arguments against expectations.
535              
536             =cut
537              
538             sub _mock_check_expectations {
539 145     145   652 my ($self, $method, $timing, @args) = @_;
540              
541 145         191 assert_not_null($method) if ASSERT;
542 145         143 assert_not_null($timing) if ASSERT;
543              
544 145         6522 my $rules_for_method = $self->_mock_expectation->{$method};
545              
546 145 100 50     817 return if not defined $rules_for_method
      66        
547             or (ref $rules_for_method || '') ne 'ARRAY';
548              
549 42         59 my $e;
550              
551             RULE:
552 42         115 foreach my $rule (@$rules_for_method) {
553 50 100       149 if (defined $rule->{at}) {
554 12 100       43 next RULE unless $timing == $rule->{at};
555             };
556              
557 42         95 eval {
558             TRY: {
559 42 100       66 if (exists $rule->{args}) {
  42         118  
560 17         50 my @rule_args = (ref $rule->{args} || '') eq 'ARRAY'
561 17 50 50     87 ? @{ $rule->{args} }
562             : ( $rule->{args} );
563              
564             # number of args matches?
565 17 100       58 next TRY unless @args == @rule_args;
566              
567             # iterate args
568 16         58 foreach my $i (0 .. @rule_args - 1) {
569 28         473 my $rule_arg = $rule_args[$i];
570 28 100 100     136 if ((ref $rule_arg || '') eq 'Regexp') {
    50          
571 6         25 assert_matches($rule_arg, $args[$i]);
572             }
573             elsif (ref $rule_arg) {
574 0         0 assert_deep_equals($rule_arg, $args[$i]);
575             }
576             else {
577 22         72 assert_equals($rule_arg, $args[$i]);
578             };
579             };
580             };
581              
582 39         471 $rule->{call} ++;
583              
584 39 100 100     188 fail( [
585             'Maximum call count (%d) for method (%s) at call (%d)',
586             $rule->{maximum}, $method, $timing
587             ] ) if (defined $rule->{maximum} and $rule->{call} > $rule->{maximum});
588              
589 36 100 100     143 fail( [
590             'Expected call count (%d) for method (%s) at call (%d)',
591             $rule->{count}, $method, $timing
592             ] ) if (defined $rule->{count} and $rule->{call} > $rule->{count});
593              
594 35 50       140 if (defined $rule->{assertion}) {
595 0 0       0 if (ref $rule->{assertion} eq 'CODE') {
596 0         0 fail( $rule->{assertion}->($method, $timing, @args) );
597             }
598             else {
599 0         0 fail( $rule->{assertion} );
600             };
601             };
602             };
603             };
604 42 100 33     3231 $e ||= Exception::Fatal->catch if $EVAL_ERROR;
605             };
606              
607 42 100       477 $e->throw if $e;
608              
609 36         82 return;
610             };
611              
612              
613             1;
614              
615              
616             =back
617              
618             =begin umlwiki
619              
620             = Class Diagram =
621              
622             [ <<role>>
623             Test::Mock::Class::Role::Object
624             -----------------------------------------------------------------------------
625             #_mock_call : HashRef
626             #_mock_expectation : HashRef
627             #_mock_action : HashRef
628             -----------------------------------------------------------------------------
629             +mock_return( method : Str, :value : Any, :at : Int, :args : ArrayRef[Any] ) : Self
630             +mock_return_at( at : Int, method : Str, :args : ArrayRef[Any] ) : Self
631             +mock_throw( method : Str, :at : Int, :exception : Str, :args : ArrayRef[Any] ) : Self
632             +mock_throw_at( at : Int, method : Str, :args : ArrayRef[Any] ) : Self
633             +mock_expect( method : Str, :at : Int, :minimum : Int, :maximum : Int, :count : Int, :args : ArrayRef[Any] ) : Self
634             +mock_expect_at( at : Int, method : Str, :args : ArrayRef[Any] ) : Self
635             +mock_expect_call_count( method : Str, count : Int, :args : ArrayRef[Any] ) : Self
636             +mock_expect_maximum_call_count( method : Str, count : Int, :args : ArrayRef[Any] ) : Self
637             +mock_expect_minimum_call_count( method : Str, count : Int, :args : ArrayRef[Any] ) : Self
638             +mock_expect_never( method : Str, :args : ArrayRef[Any] ) : Self
639             +mock_expect_once( method : Str, :args : ArrayRef[Any] ) : Self
640             +mock_expect_at_least_once( method : Str, :args : ArrayRef[Any] ) : Self
641             +mock_invoke( method : Str, args : Array ) : Any
642             +mock_tally() : Self
643             ]
644              
645             =end umlwiki
646              
647             =head1 SEE ALSO
648              
649             L<Test::Mock::Class>.
650              
651             =head1 BUGS
652              
653             The expectations and return values should be refactored as objects rather than
654             complex structure.
655              
656             The API is not stable yet and can be changed in future.
657              
658             =head1 AUTHOR
659              
660             Piotr Roszatycki <dexter@cpan.org>
661              
662             =head1 LICENSE
663              
664             Based on SimpleTest, an open source unit test framework for the PHP
665             programming language, created by Marcus Baker, Jason Sweat, Travis Swicegood,
666             Perrick Penet and Edward Z. Yang.
667              
668             Copyright (c) 2009, 2010 Piotr Roszatycki <dexter@cpan.org>.
669              
670             This program is free software; you can redistribute it and/or modify it
671             under GNU Lesser General Public License.