File Coverage

blib/lib/Class/Mock/Method/InterfaceTester.pm
Criterion Covered Total %
statement 75 76 98.6
branch 32 32 100.0
condition 6 6 100.0
subroutine 14 14 100.0
pod 1 1 100.0
total 128 129 99.2


line stmt bran cond sub pod time code
1             package Class::Mock::Method::InterfaceTester;
2              
3 1     1   33220 use strict;
  1         2  
  1         25  
4 1     1   5 use warnings;
  1         2  
  1         37  
5              
6             our $VERSION = '1.3002';
7              
8             # all this pre-amble is damned near identical to C::M::G::IT. Re-factor.
9 1     1   5 use Test::More ();
  1         2  
  1         15  
10 1     1   400 use Data::Compare;
  1         12411  
  1         6  
11 1     1   3038 use Scalar::Util qw(blessed);
  1         4  
  1         40  
12 1     1   402 use PadWalker qw(closed_over);
  1         486  
  1         49  
13 1     1   383 use Data::Dumper::Concise;
  1         6766  
  1         59  
14              
15 1     1   337 use Class::Mock::Common ();
  1         2  
  1         55  
16              
17             use Class::Mockable
18 1     1   5 _ok => sub { Test::More::ok($_[0], @_[1..$#_]) };
  1         1  
  1         9  
  0         0  
19              
20             use constant {
21 1         736 DIDNT_RUN_ALL => 'didn\'t run all tests in mock method defined in %s (remaining tests: %s)',
22             RUN_OUT => 'run out of tests on mock method defined in %s',
23              
24             WRONG_ARGS => 'wrong args to mock method defined in %s. Got %s',
25             WRONG_ARGS_W_EXPECTED => 'wrong args to mock method defined in %s. Got %s, expected %s',
26              
27             BOTH_INVOCANTS => 'bad fixture %s, can\'t have invocant_object and invocant_class, defined in %s',
28             EXP_CLASS_GOT_OBJECT => 'expected call as class method, but object method called, defined in %s',
29             EXP_OBJECT_GOT_CLASS => 'expected call as object method, but class method called, defined in %s',
30             WRONG_CLASS => 'class method called on wrong class, defined in %s - got %s expected %s',
31             WRONG_OBJECT => 'object method called on object of wrong class, defined in %s - called on a %s, expected a %s',
32             WRONG_OBJECT_SUBREF => 'object method called on object which doesn\'t match specified sub-ref, defined in %s',
33 1     1   5 };
  1         1  
34              
35             sub new {
36 11     11 1 5105 my $class = shift;
37 11         43 my $called_from = (caller(1))[3];
38              
39 11         21 my $tests = shift;
40 11         15 my @tests;
41 11 100       33 if(ref($tests) eq 'ARRAY') { @tests = @{$tests}; }
  10         17  
  10         20  
42 1         3 else { @tests = Class::Mock::Common::_get_tests_from_file(${$tests});
  1         5  
43             }
44              
45             return bless(sub {
46 28 100   28   5035 if(!@tests) { # no tests left
47 2         10 return $class->_report_error(RUN_OUT, $called_from);
48             }
49              
50 26         45 my $this_test = shift(@tests);
51 26         36 my $invocant = shift;
52 26         53 my @params = @_;
53              
54             # check arguments
55 26 100       107 if(ref($this_test->{input}) eq 'CODE') {
    100          
56 4 100       51 if(!$this_test->{input}->(@params)) {
57 2         7 return $class->_report_error(WRONG_ARGS, $called_from, Dumper(\@params));
58             }
59             } elsif(!Compare($this_test->{input}, \@params)) {
60 1         84 return $class->_report_error(WRONG_ARGS_W_EXPECTED, $called_from, Dumper(\@params), Dumper($this_test->{input}));
61             }
62              
63             # check invocant
64 23 100 100     1845 if($this_test->{invocant_class} && $this_test->{invocant_object}) {
    100          
    100          
65 1         34 return $class->_report_error(BOTH_INVOCANTS, Dumper($this_test), $called_from);
66             } elsif($this_test->{invocant_class}) { # must be called as class method on right class
67 5 100       16 if(ref($invocant)) {
    100          
68 1         3 return $class->_report_error(EXP_CLASS_GOT_OBJECT, $called_from);
69             } elsif($invocant ne $this_test->{invocant_class}) {
70 2         5 return $class->_report_error(WRONG_CLASS, $called_from, $invocant, $this_test->{invocant_class});
71             }
72             } elsif($this_test->{invocant_object}) { # must be called as object method
73 9 100       32 if(!blessed($invocant)) {
74 1         3 return $class->_report_error(EXP_OBJECT_GOT_CLASS, $called_from);
75             }
76 8 100       23 if(ref($this_test->{invocant_object}) eq 'CODE') { # check via subref
    100          
77 4 100       14 if(!$this_test->{invocant_object}->($invocant)) {
78 2         16 return $class->_report_error(WRONG_OBJECT_SUBREF, $called_from);
79             }
80             } elsif(blessed($invocant) ne $this_test->{invocant_object}) { # object must be right class
81 2         7 return $class->_report_error(WRONG_OBJECT, $called_from, blessed($invocant), $this_test->{invocant_object});
82             }
83             }
84              
85 14         30 my $output = $this_test->{output};
86             # FIXME identical code to that in C::M::Generic::InterfaceTester
87 14 100 100     34 if(
88             ref($output) eq 'REF' # ref to a ref
89 2         10 && ref(${$output}) eq 'CODE' # ... which is a ref to a sub
90             ) {
91 1         2 return ${$output}->()
  1         3  
92             } else {
93 13         87 return $output
94             }
95 11         151 }, $class);
96             }
97              
98             sub _report_error {
99 15     15   740 my($class, $error, @params) = @_;
100 15         74 $class->_ok()->(0, sprintf($error, @params));
101             }
102              
103             # re-factor this and C::M::G::IT::DESTROY
104             sub DESTROY {
105 9     9   22 my $self = shift;
106 9         21 my %closure = %{(closed_over($self))[0]};
  9         102  
107              
108 9 100       21 if(@{$closure{'@tests'}}) {
  9         135  
109 1         8 $self->_report_error(DIDNT_RUN_ALL, ${$closure{'$called_from'}}, Dumper( $closure{'@tests'} ));
  1         25  
110             }
111             }
112              
113             1;
114              
115             =head1 NAME
116              
117             Class::Mock::Method::InterfaceTester
118              
119             =head1 DESCRIPTION
120              
121             A helper for Class::Mockable's method mocking
122              
123             =head1 SYNOPSIS
124              
125             In the class under test:
126              
127             # create a '_foo' wrapper around method 'foo'
128             use Class::Mockable
129             methods => { _foo => 'foo' };
130              
131             And then in the tests:
132              
133             Some::Module->_set_foo(
134             Class::Mock::Method::InterfaceTester->new([
135             {
136             input => ...
137             output => ...
138             }
139             ])
140             );
141              
142             =head1 METHODS
143              
144             =head2 new
145              
146             This is the constructor. It returns a blessed sub-ref. Class::Mockable's
147             method mocking expects a sub-ref, so will Just Work (tm).
148              
149             The sub-ref will behave similarly to the method calls defined in
150             Class::Mock::Generic::InterfaceTester. That is, it will validate
151             that the method is being called correctly and emit a test failure if it
152             isn't, or if called correctly will return the specified value. If the
153             method is ever called with the wrong parameters - including if defined
154             method calls are made in the wrong order - then that's a test failure.
155              
156             It is also a test failure to call the method fewer or more times than
157             expected. Calling it fewer times than expected will be detected very
158             late - when the subroutine goes away, so either at the end of the process
159             or when it is redefined, eg with _reset_... (see Class::Mockable).
160              
161             C takes an arrayref of hashrefs as its argument. Those hashes
162             must have keys 'input' and 'output' whose values define the ins and
163             outs of each method call in turn.
164              
165             =over
166              
167             =item input
168              
169             This is normally an arrayref which will get compared to all the method's
170             arguments (excluding the first one, the object or class itself) but for
171             validating very complex inputs you may specify a subroutine reference for the
172             input, which will get executed with the actual input as its argument, and emit
173             a failure if the call returns false.
174              
175             =item output
176              
177             This is normally just whatever you want to return, but as a special case
178             you can specify a B to a code-ref. If you do that then the code-ref
179             will be executed and whatever *it* returns will be returned.
180              
181             =back
182              
183             If you want to check
184             that the method is being invoked on the right object or class (if you
185             are paranoid about inheritance, for example) then use the optional
186             'invocant_class' string to check that it's being called as a class method
187             on the right class (not on a subclass, *the right class*), or
188             invocant_object' string to check that it's being called on an object of
189             the right class (again, not a subclass), or 'invocant_object' subref to
190             check that it's being called on an object that, when passed to the sub-ref,
191             returns true.
192              
193             Alternatively, C can read fixture data from a file.
194              
195             Recording fixtures to a file is not yet implemented.
196              
197             =cut
198              
199             # or you can use it
200             # to pass args through to other code and record its responses to a file.
201              
202             =over
203              
204             =item reading from a file
205              
206             Pass a reference to a scalar as the first argument. Any subsequent arguments
207             will be ignored:
208              
209             Class::Mock::Method::InterfaceTester->new(\"filename.dd");
210              
211             Yes, that's a reference to a scalar. The scalar is assumed to be a filename
212             which will be read, and whose contents should be valid arguments to create
213             fixtures.
214              
215             =cut
216              
217             # =item recording and writing to a file
218             #
219             # Set the environment varilable PERL_CMMIT_RECORD to a true value and Pass a
220             # reference to a scalar as the first argument, following by the name of class
221             # whose interactions you want to record, and optionally either a list of method
222             # names or a regular expression matching some method names:
223             #
224             # Class::Mock::Method::InterfaceTester->new(
225             # \"filename.dd",
226             # 'I::Want::To::Mock::This',
227             # qw(but only these methods) # or qr/^(but|only|these|methods)$/
228             # );
229             #
230             # In the absence of a list of methods (or a regex) then all methods will be
231             # recorded, including those inherited from superclasses, except those whose names
232             # begin with an underscore.
233              
234             =back
235              
236             =cut
237              
238             # The observant amongst you will have noticed that because when reading from
239             # a file all arguments after the first are ignored, then you can choose to
240             # record or to playback by just setting the environment variable and making
241             # no other changes. This is deliberate.
242              
243             =head1 SEE ALSO
244              
245             L
246              
247             L
248              
249             =head1 AUTHOR
250              
251             Copyright 2013 UK2 Ltd and David Cantrell Edavid@cantrell.org.ukE
252              
253             This software is free-as-in-speech software, and may be used, distributed,
254             and modified under the terms of either the GNU General Public Licence
255             version 2 or the Artistic Licence. It's up to you which one you use. The
256             full text of the licences can be found in the files GPL2.txt and
257             ARTISTIC.txt, respectively.
258              
259             =head1 SOURCE CODE REPOSITORY
260              
261             Egit://github.com/DrHyde/perl-modules-Class-Mockable.gitE
262              
263             =head1 BUGS/FEEDBACK
264              
265             Please report bugs at Github
266             Ehttps://github.com/DrHyde/perl-modules-Class-Mockable/issuesE
267              
268             =head1 CONSPIRACY
269              
270             This software is also free-as-in-mason.
271              
272             =cut