File Coverage

blib/lib/Class/Mock/Generic/InterfaceTester.pm
Criterion Covered Total %
statement 67 68 98.5
branch 22 22 100.0
condition 9 9 100.0
subroutine 11 11 100.0
pod 1 1 100.0
total 110 111 99.1


line stmt bran cond sub pod time code
1             package Class::Mock::Generic::InterfaceTester;
2              
3 1     1   93104 use strict;
  1         2  
  1         30  
4 1     1   6 use warnings;
  1         2  
  1         43  
5              
6             our $VERSION = '1.3000';
7              
8 1     1   5 use vars qw($AUTOLOAD);
  1         2  
  1         35  
9              
10 1     1   6 use Test::More ();
  1         2  
  1         15  
11 1     1   457 use Data::Compare;
  1         10511  
  1         7  
12 1     1   3461 use Scalar::Util;
  1         4  
  1         45  
13 1     1   561 use Data::Dumper;
  1         6553  
  1         125  
14             local $Data::Dumper::Indent = 1;
15              
16             use Class::Mockable
17 1     1   413 _ok => sub { Test::More::ok($_[0], @_[1..$#_]) };
  1         3  
  1         10  
  0         0  
18              
19             =head1 NAME
20              
21             Class::Mock::Generic::InterfaceTester
22              
23             =head1 DESCRIPTION
24              
25             A mock object for testing that you call other code correctly
26              
27             =head1 SYNOPSIS
28              
29             In the code under test:
30              
31             package My::Module;
32              
33             use Class::Mockable
34             _storage_class => 'MyApp::Storage';
35              
36             and in the tests:
37              
38             My::Module->_storage_class(
39             Class::Mock::Generic::InterfaceTester->new([
40             {
41             method => 'fetch',
42             input => [customer_id => 94],
43             output => ...
44             },
45             {
46             method => 'update',
47             input => [status => 'fired', reason => 'non-payment'],
48             output => 1,
49             },
50             ...
51             ]);
52             );
53              
54             or, more simply:
55              
56             my $interface_tester = Class::Mock::Generic::InterfaceTester->new;
57             My::Module->_storage_class($interface_tester);
58            
59             # Expect this method to be called by this test.
60             $interface_tester->add_fixtures(
61             fetch => {
62             input => [customer_id => 94],
63             output => ...
64             },
65             );
66             ok(My::Module->something_that_fetches_from_storage(customer_id => 94));
67              
68             # Expect these two methods to be called by this next test.
69             $interface_tester->add_fixtures(
70             update => {
71             input => [status => 'fired', reason => 'non-payment'],
72             output => 1,
73             },
74             uuid => {
75             output => 'DEADBEEF-1234-5678-9ABC-1234567890AB',
76             }
77             );
78             ok(My::Module->something_that_updates_storage_for_non_payment);
79              
80             =head1 METHODS
81              
82             =head2 new
83              
84             This is the main method. It creates a very simple object. Pass to it a list or
85             arrayref of fixtures (see L for syntax). Any subsequent method
86             calls on that object are handled by AUTOLOAD. Note that because
87             the constructor is Highly Magical you can even provide fixtures for a
88             method called 'new()'. The only ones you can't provide fixtures for are
89             'AUTOLOAD()' and 'DESTROY()', and possibly L.
90              
91             For each method call, the first element is removed from the array of
92             fixtures. We then compare the name of the method that was called with
93             the name of the method we *expected* to be called. If it's wrong, a
94             test failure is emitted. If that matches, we then compare the actual
95             parameters passed to the method with those in the fixture. If they don't
96             match, then that's a test failure. If they do match, then finally the
97             'output' specified in the fixture is returned.
98              
99             Test failures will tell you what the error was, and where the object was created.
100              
101             If you want to do anything more complicated than compare input exactly,
102             then specify a code-ref thus:
103              
104             {
105             method => 'update',
106             input => sub { exists({@_}->{fruit}) && {@_}->{fruit} eq 'apple' },
107             output => 94
108             }
109              
110             In this case, the actual parameters passed to the method will be passed to
111             that code-ref for validation. It should return true if the params are OK
112             and false otherwise. In the example, it will return true if the hash of
113             args contains a 'fruit' key with value 'apple'.
114              
115             If you want to do something more complicated than just return a fixed value
116             then specify a B to a code-ref for the output thus:
117              
118             {
119             method => 'next_value',
120             input => 94,
121             output => \sub { ... }
122             }
123              
124             Note that it must be a reference to a code-ref, to distinguish from the case where
125             you really do want to return a code-ref. The code-ref supplied will be executed and
126             whatever it returns will be returned. If you want to return a reference to a code-ref
127             then you can perpetrate a mess like this:
128              
129             output => sub { \sub { ... } }
130              
131             =head2 add_fixtures
132              
133             Supplied with either an arrayref or a list of method call fixtures, adds them
134             to the array of fixtures this object maintains internally (although see below
135             for a caveat about this).
136              
137             At the simplest, a method call fixture is a hashref with keys
138             C, C and C. If you don't care about the input
139             your method receives, you can omit that key and any input will be accepted.
140              
141             You can also provide a fixture as a pair of C and (hashref containing
142             input and output). This lets you write a series of method call fixtures as an
143             apparent ordered hash, which may feel more natural. As above, you can omit
144             the input field if you don't care. So the following calls are equivalent:
145              
146             $interface_tester->add_fixtures(
147             [
148             {
149             method => 'do_something',
150             input => sub { 1 },
151             output => 'Yup, done',
152             },
153             {
154             method => 'do_something_with_this',
155             input => ['fish'],
156             output => 'Fish cooked',
157             }
158             ]
159             );
160              
161             $interface_tester->add_fixtures(
162             do_something => { output => 'Yup, done' },
163             do_something_with_this => {
164             input => ['fish'],
165             output => 'Fish cooked',
166             },
167             );
168              
169             Caveat: just in case you need to test a call to a method that coincidentally
170             is also called C, this method is only enabled
171             if you did I provide a list of fixtures to the constructor. Note that this
172             means that you can't use C to add a fixture for a method called
173             C!
174              
175             =head2 set_name
176              
177             Takes a scalar parameter and spits that back out at you in any errors, which
178             may make debugging code that used this module easier. This method is only
179             available before you add fixtures. As soon as you add fixtures any calls to
180             C are treated as normal mocked method calls.
181              
182             =head2 DESTROY
183              
184             When the mock object goes out of scope, this is called as usual. It
185             will emit a test failure if not all the fixtures were used.
186              
187             =head1 PHILOSOPHY
188              
189             When you test a piece of code, you want to test it in isolation, because
190             that way when you get test failures it's much easier to find them than if
191             the code you're testing then calls other code, which calls three other
192             modules, which call other modules and so on. If your tests end up running
193             a whole bunch of code other than just the little bit you actually want to
194             test then a failure in any one of those other parts can be very hard to
195             find and fix.
196              
197             You also want to test all of your code's inputs and outputs. Some inputs
198             and outputs are obvious - the parameters you pass to a method are inputs,
199             and its outputs include the return value and any changes in state that the
200             method call makes. For example, in this accessor:
201              
202             package MyApp::SomeModule;
203              
204             sub fruit {
205             my $self = shift;
206             if(@_) { $self->{fruit} = shift; }
207             return $self->{fruit};
208             }
209              
210             the inputs are the argument (if supplied), and the outputs are the return
211             value and, if you supplied an argument, the object's changed internal state.
212              
213             So far, so easy to test.
214              
215             Now consider a slightly more complex accessor:
216              
217             package MyApp::SomeModule;
218              
219             sub fruit {
220             my $self = shift;
221             if(@_) {
222             $self->{fruit} = shift;
223             $self->log(INFO, "fruit changed to ".$self->{fruit});
224             }
225             return $self->{fruit};
226             }
227              
228             sub log {
229             my $self = shift;
230             my $priority = shift;
231             my $message = shift;
232             MyApp::Logger->log($priority, $message);
233             }
234              
235             This accessor has an extra output, the call to $self->log(), the method for
236             which is also shown. But when you're testing the accessor, you don't really
237             want the hassle of setting up and configuring logging, nor do you really want to
238             run all the extra code that that entails, all of which is a potential source
239             of confusing test failures and should itself be run in isolation. So, modify
240             the log() method thus:
241              
242             package MyApp::SomeModule;
243              
244             use Class::Mockable
245             _logger => 'MyApp::Logger';
246              
247             sub log {
248             my $self = shift;
249             my $priority = shift;
250             my $message = shift;
251             $self->_logger()->log($priority, $message);
252             }
253            
254             and in the tests ...
255              
256             MyApp::SomeModule->_logger(
257             Class::Mock::Generic::InterfaceTester->new([
258             {
259             method => 'log',
260             input => [INFO, "fruit changed to apple"],
261             output => "doesn't matter for this test"
262             }
263             ])
264             );
265              
266             ...
267             ok($object->fruit('apple') eq 'apple',
268             "'fruit' accessor returned the right value");
269             ok($object->fruit() eq 'apple',
270             "... yup, the object's internal state looks like it changed");
271              
272             That mocks the logger, but still checks that your code called it correctly.
273             The mocking being in the log() method means that the only application code that
274             got run for this test is the fruit() accessor and the log() method - the logger
275             itself wasn't run, it was mocked - so we have proved that all of the fruit()
276             accessor's inputs and outputs, including the method calls that it makes, are
277             correct.
278              
279             If the log() method call (and hence the call to the mocked logger) is correct,
280             then you shouldn't notice any changes in your tests. But if the accessor's
281             calling of the log() method changes in any way without you also changing the
282             mock (which is effectively a test fixture) then you'll get test failures.
283              
284             =head1 SEE ALSO
285              
286             L is good for faking up troublesome interfaces to
287             third-party systems - for example, for making a wee pretendy third
288             party web service that the code you're testing wants to talk to. You want
289             to mock such things if the third party service is slow, or unreliable, or
290             not available in all your testing environments. You could also use
291             Class::Mock::Generic::InterfaceTester for this, but often Test::MockObject
292             is simpler. Use Test::MockObject if you care mostly about the data you get
293             back from external code, use Class::Mock::Generic::InterfaceTester if you
294             care more about how you call external code.
295              
296             =cut
297              
298             my $_add_fixtures;
299              
300             sub new {
301 17     17 1 153 my $class = shift;
302              
303             # If we're mocking a new method, we don't want to reconstruct the mock
304             # object.
305 17 100       63 if(Scalar::Util::blessed($class)) {
306 2         6 $AUTOLOAD = __PACKAGE__.'::new';
307 2         5 return $class->AUTOLOAD(@_);
308             }
309              
310 15         168 my($sub, $line, $file) = ((caller(1))[3], (caller(0))[2, 1]);
311 15         88 my $caller = sprintf("defined in %s at line %d of %s", $sub, $line, $file);
312 15         65 my $self = bless({
313             called_from => $caller,
314             tests => [],
315             }, $class);
316 15         43 $self->{_fixtures_have_been_set} = 0;
317 15 100       42 if (@_) {
318 9         27 $_add_fixtures->($self, @_);
319             } else {
320 6         13 $self->{_no_fixtures_in_constructor} = 1;
321             }
322 15         44 return $self;
323             }
324              
325             # Declaring this as a coderef rather than a method so we can decide
326             # whether it exists or not based on how the constructor was called,
327             # for maximum backwards-compatibility.
328              
329             $_add_fixtures = sub {
330             my $self = shift;
331              
332             $self->{_fixtures_have_been_set} = 1;
333              
334             # We might have been passed an arrayref or a list.
335             my @args = (ref($_[0]) eq 'ARRAY' && @_ == 1) ? @{$_[0]} : @_;
336              
337             # Our fixtures might be raw hashrefs, or method name => hashref pairs.
338             # You can't mix and match.
339             my @fixtures;
340             if (ref($args[0]) eq 'HASH') {
341             @fixtures = @args;
342             } else {
343             while (my ($method, $fixture_details) = splice(@args, 0, 2)) {
344             push @fixtures, { method => $method, %$fixture_details };
345             }
346             }
347              
348             # If input is omitted, we assume we don't care.
349             for (@fixtures) {
350             if (!exists $_->{input}) {
351             $_->{input} = sub { 1 };
352             }
353             }
354              
355             # OK, add these fixtures.
356             push @{ $self->{tests} ||= [] }, @fixtures;
357             };
358              
359             sub AUTOLOAD {
360 36     36   2474 (my $method = $AUTOLOAD) =~ s/.*:://;
361 36         79 my $self = shift;
362 36         112 my @args = @_;
363              
364             # If this is the special method add_fixtures, and we didn't
365             # add fixtures in the constructor (i.e. we expect to add fixtures
366             # bit by bit rather than all at once), add fixtures to our list.
367 36 100 100     175 if ($method eq 'add_fixtures' && $self->{_no_fixtures_in_constructor}) {
    100 100        
368 7         19 return $_add_fixtures->($self, @args);
369             # If we haven't set any fixtures at all then we can assume that the
370             # 'set_name' method is supposed to set this object's name
371             } elsif($method eq 'set_name' && !$self->{_fixtures_have_been_set}) {
372 1         17 $self->{called_from} = "'$args[0]' ".$self->{called_from};
373 1         3 return;
374             }
375              
376             # If we have no more tests, then we've called the mocked $thing more
377             # times than expected - the code under test obviously has more outputs
378             # than expected, which is Bad.
379 28 100       58 if(!@{$self->{tests}}) {
  28         69  
380             __PACKAGE__->_ok()->(0, sprintf (
381             "run out of tests on mock object %s",
382             $self->{called_from}
383 4         28 ));
384 4         1151 return;
385             }
386              
387 24         40 my $next_test = shift(@{$self->{tests}});
  24         52  
388              
389             # Check the correct method was called. If it wasn't, then the code
390             # under test's outputs are not what we expected (they are, at best
391             # in the wrong order), which is Bad.
392 24 100       64 if($next_test->{method} ne $method) {
393             __PACKAGE__->_ok()->( 0,
394             sprintf (
395             "wrong method '%s' (expected '%s') called on mock object %s",
396             $method,
397             $next_test->{method},
398             $self->{called_from},
399             )
400 1         15 );
401 1         340 return;
402             }
403              
404             # Now ensure that the input was as expected. The fixture is normally
405             # provided as an arrayref of expected params, which is (deeply) compared
406             # to what was provided. For more complicated stuff such as where you
407             # are passing an object, or where you just want to check that the args
408             # match a certain pattern (eg did the hash of args contain a 'fruit' key
409             # with value 'apple') then pass in a code-ref.
410 23 100       107 if (ref $next_test->{input} eq 'CODE') {
    100          
411             # pass the args to the code, see if it says they're ok
412 8 100       22 if(!$next_test->{input}->(@args)) {
413             __PACKAGE__->_ok()->(0,
414             sprintf (
415             "wrong args to mock object %s. Got %s.",
416             $self->{called_from},
417 1         8 Dumper(\@args)
418             )
419             );
420             }
421             } elsif (!Compare(\@args, $next_test->{input})) {
422             __PACKAGE__->_ok()->( 0,
423             sprintf (
424             "wrong args to mock object %s (expected %s, got %s)",
425             $self->{called_from},
426 3         319 Dumper($next_test->{input}),
427             Dumper(\@args)
428             )
429             );
430 3         1287 return;
431             }
432 20         1942 my $output = $next_test->{output};
433 20 100 100     63 if(
434             ref($output) eq 'REF' # ref to a ref
435 3         12 && ref(${$output}) eq 'CODE' # ... which is a ref to a sub
436             ) {
437 2         5 return ${$output}->()
  2         6  
438             } else {
439 18         170 return $output
440             }
441             }
442              
443             sub DESTROY {
444 15     15   3873 my $self = shift;
445 15 100       24 if(@{$self->{tests}}) {
  15         235  
446             __PACKAGE__->_ok()->( 0,
447             sprintf (
448             "didn't run all tests in mock object %s (remaining tests: %s)",
449             $self->{called_from},
450 1         5 Dumper( $self->{tests} ),
451             )
452             );
453             }
454             }
455            
456             =head1 AUTHOR
457              
458             Copyright 2012, 2017 UK2 Ltd and David Cantrell Edavid@cantrell.org.ukE
459              
460             Some contributions from Sam Kington
461              
462             This software is free-as-in-speech software, and may be used, distributed,
463             and modified under the terms of either the GNU General Public Licence
464             version 2 or the Artistic Licence. It's up to you which one you use. The
465             full text of the licences can be found in the files GPL2.txt and
466             ARTISTIC.txt, respectively.
467              
468             =head1 SOURCE CODE REPOSITORY
469              
470             Egit://github.com/DrHyde/perl-modules-Class-Mockable.gitE
471              
472             =head1 BUGS/FEEDBACK
473              
474             Please report bugs at Github
475             Ehttps://github.com/DrHyde/perl-modules-Class-Mockable/issuesE
476              
477             =head1 CONSPIRACY
478              
479             This software is also free-as-in-mason.
480              
481             =cut
482              
483             1;