File Coverage

blib/lib/Class/Mock/Method/InterfaceTester.pm
Criterion Covered Total %
statement 67 68 98.5
branch 30 30 100.0
condition 6 6 100.0
subroutine 13 13 100.0
pod 1 1 100.0
total 117 118 99.1


line stmt bran cond sub pod time code
1             package Class::Mock::Method::InterfaceTester;
2              
3 1     1   36858 use strict;
  1         8  
  1         28  
4 1     1   11 use warnings;
  1         2  
  1         42  
5              
6             our $VERSION = '1.3000';
7              
8             # all this pre-amble is damned near identical to C::M::G::IT. Re-factor.
9 1     1   4 use Test::More ();
  1         7  
  1         15  
10 1     1   486 use Data::Compare;
  1         11092  
  1         8  
11 1     1   3467 use Scalar::Util qw(blessed);
  1         2  
  1         50  
12 1     1   469 use PadWalker qw(closed_over);
  1         599  
  1         59  
13 1     1   613 use Data::Dumper;
  1         7130  
  1         99  
14             local $Data::Dumper::Indent = 1;
15              
16             use Class::Mockable
17 1     1   8 _ok => sub { Test::More::ok($_[0], @_[1..$#_]) };
  1         2  
  1         11  
  0         0  
18              
19             use constant {
20 1         706 DIDNT_RUN_ALL => 'didn\'t run all tests in mock method defined in %s (remaining tests: %s)',
21             RUN_OUT => 'run out of tests on mock method defined in %s',
22              
23             WRONG_ARGS => 'wrong args to mock method defined in %s. Got %s',
24             WRONG_ARGS_W_EXPECTED => 'wrong args to mock method defined in %s. Got %s, expected %s',
25              
26             BOTH_INVOCANTS => 'bad fixture %s, can\'t have invocant_object and invocant_class, defined in %s',
27             EXP_CLASS_GOT_OBJECT => 'expected call as class method, but object method called, defined in %s',
28             EXP_OBJECT_GOT_CLASS => 'expected call as object method, but class method called, defined in %s',
29             WRONG_CLASS => 'class method called on wrong class, defined in %s - got %s expected %s',
30             WRONG_OBJECT => 'object method called on object of wrong class, defined in %s - called on a %s, expected a %s',
31             WRONG_OBJECT_SUBREF => 'object method called on object which doesn\'t match specified sub-ref, defined in %s',
32 1     1   6 };
  1         2  
33              
34             sub new {
35 10     10 1 5582 my $class = shift;
36 10         52 my $called_from = (caller(1))[3];
37 10         21 my @tests = @{shift()};
  10         57  
38              
39             return bless(sub {
40 26 100   26   6368 if(!@tests) { # no tests left
41 2         10 return $class->_report_error(RUN_OUT, $called_from);
42             }
43              
44 24         46 my $this_test = shift(@tests);
45 24         43 my $invocant = shift;
46 24         54 my @params = @_;
47              
48             # check arguments
49 24 100       135 if(ref($this_test->{input}) eq 'CODE') {
    100          
50 2 100       7 if(!$this_test->{input}->(@params)) {
51 1         9 return $class->_report_error(WRONG_ARGS, $called_from, Dumper(\@params));
52             }
53             } elsif(!Compare($this_test->{input}, \@params)) {
54 1         101 return $class->_report_error(WRONG_ARGS_W_EXPECTED, $called_from, Dumper(\@params), Dumper($this_test->{input}));
55             }
56              
57             # check invocant
58 22 100 100     2274 if($this_test->{invocant_class} && $this_test->{invocant_object}) {
    100          
    100          
59 1         5 return $class->_report_error(BOTH_INVOCANTS, Dumper($this_test), $called_from);
60             } elsif($this_test->{invocant_class}) { # must be called as class method on right class
61 5 100       19 if(ref($invocant)) {
    100          
62 1         4 return $class->_report_error(EXP_CLASS_GOT_OBJECT, $called_from);
63             } elsif($invocant ne $this_test->{invocant_class}) {
64 2         13 return $class->_report_error(WRONG_CLASS, $called_from, $invocant, $this_test->{invocant_class});
65             }
66             } elsif($this_test->{invocant_object}) { # must be called as object method
67 9 100       37 if(!blessed($invocant)) {
68 1         4 return $class->_report_error(EXP_OBJECT_GOT_CLASS, $called_from);
69             }
70 8 100       29 if(ref($this_test->{invocant_object}) eq 'CODE') { # check via subref
    100          
71 4 100       11 if(!$this_test->{invocant_object}->($invocant)) {
72 2         14 return $class->_report_error(WRONG_OBJECT_SUBREF, $called_from);
73             }
74             } elsif(blessed($invocant) ne $this_test->{invocant_object}) { # object must be right class
75 2         22 return $class->_report_error(WRONG_OBJECT, $called_from, blessed($invocant), $this_test->{invocant_object});
76             }
77             }
78              
79 13         34 my $output = $this_test->{output};
80             # FIXME identical code to that in C::M::Generic::InterfaceTester
81 13 100 100     42 if(
82             ref($output) eq 'REF' # ref to a ref
83 2         10 && ref(${$output}) eq 'CODE' # ... which is a ref to a sub
84             ) {
85 1         2 return ${$output}->()
  1         3  
86             } else {
87 12         82 return $output
88             }
89 10         131 }, $class);
90             }
91              
92             sub _report_error {
93 14     14   463 my($class, $error, @params) = @_;
94 14         88 $class->_ok()->(0, sprintf($error, @params));
95             }
96              
97             # re-factor this and C::M::G::IT::DESTROY
98             sub DESTROY {
99 8     8   23 my $self = shift;
100 8         15 my %closure = %{(closed_over($self))[0]};
  8         111  
101              
102 8 100       24 if(@{$closure{'@tests'}}) {
  8         120  
103 1         2 $self->_report_error(DIDNT_RUN_ALL, ${$closure{'$called_from'}}, Dumper( $closure{'@tests'} ));
  1         20  
104             }
105             }
106              
107             1;
108              
109             =head1 NAME
110              
111             Class::Mock::Method::InterfaceTester
112              
113             =head1 DESCRIPTION
114              
115             A helper for Class::Mockable's method mocking
116              
117             =head1 SYNOPSIS
118              
119             In the class under test:
120              
121             # create a '_foo' wrapper around method 'foo'
122             use Class::Mockable
123             methods => { _foo => 'foo' };
124              
125             And then in the tests:
126              
127             Some::Module->_set_foo(
128             Class::Mock::Method::InterfaceTester->new([
129             {
130             input => ...
131             output => ...
132             }
133             ])
134             );
135              
136             =head1 METHODS
137              
138             =head2 new
139              
140             This is the constructor. It returns a blessed sub-ref. Class::Mockable's
141             method mocking expects a sub-ref, so will Just Work (tm).
142              
143             The sub-ref will behave similarly to the method calls defined in
144             Class::Mock::Generic::InterfaceTester. That is, it will validate
145             that the method is being called correctly and emit a test failure if it
146             isn't, or if called correctly will return the specified value. If the
147             method is ever called with the wrong parameters - including if defined
148             method calls are made in the wrong order - then that's a test failure.
149              
150             It is also a test failure to call the method fewer or more times than
151             expected. Calling it fewer times than expected will be detected very
152             late - when the subroutine goes away, so either at the end of the process
153             or when it is redefined, eg with _reset_... (see Class::Mockable).
154              
155             C takes an arrayref of hashrefs as its argument. Those hashes
156             must have keys 'input' and 'output' whose values define the ins and
157             outs of each method call in turn.
158              
159             =over
160              
161             =item input
162              
163             This is normally an arrayref which will get compared to all the method's
164             arguments (excluding the first one, the object or class itself) but for
165             validating very complex inputs you may specify a subroutine reference for the
166             input, which will get executed with the actual input as its argument, and emit
167             a failure if the call returns false.
168              
169             =item output
170              
171             This is normally just whatever you want to return, but as a special case
172             you can specify a B to a code-ref. If you do that then the code-ref
173             will be executed and whatever *it* returns will be returned.
174              
175             =back
176              
177             If you want to check
178             that the method is being invoked on the right object or class (if you
179             are paranoid about inheritance, for example) then use the optional
180             'invocant_class' string to check that it's being called as a class method
181             on the right class (not on a subclass, *the right class*), or
182             invocant_object' string to check that it's being called on an object of
183             the right class (again, not a subclass), or 'invocant_object' subref to
184             check that it's being called on an object that, when passed to the sub-ref,
185             returns true.
186              
187             =head1 SEE ALSO
188              
189             L
190              
191             L
192              
193             =head1 AUTHOR
194              
195             Copyright 2013 UK2 Ltd and David Cantrell Edavid@cantrell.org.ukE
196              
197             This software is free-as-in-speech software, and may be used, distributed,
198             and modified under the terms of either the GNU General Public Licence
199             version 2 or the Artistic Licence. It's up to you which one you use. The
200             full text of the licences can be found in the files GPL2.txt and
201             ARTISTIC.txt, respectively.
202              
203             =head1 SOURCE CODE REPOSITORY
204              
205             Egit://github.com/DrHyde/perl-modules-Class-Mockable.gitE
206              
207             =head1 BUGS/FEEDBACK
208              
209             Please report bugs at Github
210             Ehttps://github.com/DrHyde/perl-modules-Class-Mockable/issuesE
211              
212             =head1 CONSPIRACY
213              
214             This software is also free-as-in-mason.
215              
216             =cut