File Coverage

blib/lib/Test/ExpectAndCheck.pm
Criterion Covered Total %
statement 143 155 92.2
branch 31 40 77.5
condition 4 6 66.6
subroutine 33 35 94.2
pod 4 4 100.0
total 215 240 89.5


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk
5              
6             package Test::ExpectAndCheck;
7              
8 4     4   106624 use strict;
  4         13  
  4         84  
9 4     4   14 use warnings;
  4         6  
  4         106  
10              
11             our $VERSION = '0.05';
12              
13 4     4   16 use Carp;
  4         5  
  4         161  
14              
15 4     4   17 use List::Util qw( first );
  4         7  
  4         279  
16 4     4   20 use Scalar::Util qw( blessed );
  4         19  
  4         141  
17              
18 4     4   1862 use Test::Deep ();
  4         29054  
  4         93  
19              
20 4     4   21 use constant EXPECTATION_CLASS => "Test::ExpectAndCheck::_Expectation";
  4         4  
  4         3262  
21              
22             =head1 NAME
23              
24             C - C-style unit testing with object methods
25              
26             =head1 SYNOPSIS
27              
28             use Test::More;
29             use Test::ExpectAndCheck;
30              
31             my ( $controller, $mock ) = Test::ExpectAndCheck->create;
32              
33             {
34             $controller->expect( act => 123, 45 )
35             ->will_return( 678 );
36              
37             is( $mock->act( 123, 45 ), 678, '$mock->act returns result' );
38              
39             $controller->check_and_clear( '->act' );
40             }
41              
42             done_testing;
43              
44             =head1 DESCRIPTION
45              
46             This package creates objects that assist in writing unit tests with mocked
47             object instances. Each mock instance will expect to receive a given list of
48             method calls. Each method call is checked that it received the right
49             arguments, and will return a prescribed result. At the end of each test, each
50             object is checked to ensure all the expected methods were called.
51              
52             =cut
53              
54             =head1 METHODS
55              
56             =cut
57              
58             =head2 create
59              
60             ( $controller, $mock ) = Test::ExpectAndCheck->create;
61              
62             Objects are created in "entangled pairs" by the C method. The first
63             object is called the "controller", and is used by the unit testing script to
64             set up what method calls are to be expected, and what their results shall be.
65             The second object is the "mock", the object to be passed to the code being
66             tested, on which the expected method calls are (hopefully) invoked. It will
67             have whatever interface is implied by the method call expectations.
68              
69             =cut
70              
71             sub create
72             {
73 3     3 1 184 my $class = shift;
74              
75 3         10 my $controller = bless {
76             expectations => [],
77             whenever => {},
78             }, $class;
79 3         15 my $mock = Test::ExpectAndCheck::_Obj->new( $controller );
80              
81 3         9 return ( $controller, $mock );
82             }
83              
84             =head2 expect
85              
86             $exp = $controller->expect( $method, @args );
87              
88             Specifies that the mock will expect to receive a method call of the given
89             name, with the given arguments.
90              
91             The argument values are compared using L. Values can
92             be specified literally, or using any of the "Special Comparisons" defined by
93             L.
94              
95             The test script can call the L or L methods on the
96             expectation to set what the result of invoking this method will be.
97              
98             =cut
99              
100             sub expect
101             {
102 15     15 1 25425 my $self = shift;
103 15         28 my ( $method, @args ) = @_;
104              
105 15         22 my ( undef, $file, $line ) = caller(1);
106 15 50       82 defined $file or ( undef, $file, $line ) = caller(0);
107              
108 15         26 push @{ $self->{expectations} }, my $exp = $self->EXPECTATION_CLASS->new(
  15         63  
109             $method => [ @args ], $file, $line,
110             );
111              
112 15         46 return $exp;
113             }
114              
115             =head2 whenever
116              
117             $exp = $controller->whenever( $method, @args );
118              
119             I
120              
121             Specifies that the mock might expect to receive method calls of the given name
122             with the given arguments. These expectations are not expired once called, nor
123             do they expect to be called in any particular order. Furthermore it is not a
124             test failure for one of these not to be invoked at all.
125              
126             These expectations do not directly form part of the test assertions checked by
127             the L method, but they may be useful to assist the code
128             under test, such as providing support behaviours that it may rely on but would
129             make the test script too fragile if spelled out in full using a regular
130             C.
131              
132             These expectations are only used as a fallback mechanism, if the next real
133             C-based expectation does not match a method call. Individual special
134             cases can still be set up using C even though a C exists
135             that might also match it.
136              
137             As with L, the argument values are compared using C, and
138             results can be set with L or L.
139              
140             =cut
141              
142             sub whenever
143             {
144 5     5 1 5971 my $self = shift;
145 5         10 my ( $method, @args ) = @_;
146              
147 5         8 my ( undef, $file, $line ) = caller(1);
148 5 50       29 defined $file or ( undef $file, $line ) = caller(0);
149              
150 5         6 push @{ $self->{whenever}{$method} }, my $exp = $self->EXPECTATION_CLASS->new(
  5         35  
151             $method => [ @args ], $file, $line,
152             );
153              
154 5         18 return $exp;
155             }
156              
157             sub _stringify
158             {
159 8     8   13 my ( $v ) = @_;
160 8 50 33     82 if( !defined $v ) {
    50          
    100          
    50          
161 0         0 return "undef";
162             }
163             elsif( blessed $v and $v->isa( "Test::Deep::Ignore" ) ) {
164 0         0 return "ignore()";
165             }
166             elsif( $v =~ m/^-?[0-9]+$/ ) {
167 2         206 return sprintf "%d", $v;
168             }
169             elsif( $v =~ m/^[\x20-\x7E]*\z/ ) {
170 6         15 $v =~ s/([\\'])/\\$1/g;
171 6         45 return qq('$v');
172             }
173             else {
174 0 0       0 if( $v =~ m/[^\n\x20-\x7E]/ ) {
175             # string contains something non-printable; just hexdump it all
176 0         0 $v =~ s{(.)}{sprintf "\\x%02X", ord $1}gse;
  0         0  
177             }
178             else {
179 0         0 $v =~ s/([\\'\$\@])/\\$1/g;
180 0         0 $v =~ s{\n}{\\n}g;
181             }
182 0         0 return qq("$v");
183             }
184             }
185              
186             sub _stringify_args
187             {
188 18     18   48 join ", ", map { _stringify $_ } @_;
  8         12  
189             }
190              
191             sub _call
192             {
193 25     25   28 my $self = shift;
194 25         44 my ( $method, @args ) = @_;
195              
196 25         27 my $e;
197 25 100 100 14   62 $e = first { !$_->_called } @{ $self->{expectations} } and
  14         25  
  25         73  
198             $e->_consume( $method, @args ) and
199             return $e->_result( @args );
200              
201 12 100   12   37 if( my $wh = first { $_->_consume( $method, @args ) } @{ $self->{whenever}{$method} } ) {
  12         23  
  12         34  
202 10         20 return $wh->_result( @args );
203             }
204              
205 2         6 my $message = Carp::shortmess( "Unexpected call to ->$method(${\ _stringify_args @args })" );
  2         4  
206 2 100       60 $message .= "... while expecting " . $e->_stringify if $e;
207 2 100       5 $message .= "... after all expectations done" if !$e;
208 2         11 die "$message.\n";
209             }
210              
211             =head2 check_and_clear
212              
213             $controller->check_and_clear( $name );
214              
215             Checks that by now, every expected method has been called, and emits a new
216             test output line via L. Regardless, the expectations are also
217             cleared out ready for the start of the next test.
218              
219             =cut
220              
221             sub check_and_clear
222             {
223 21     21 1 3719 my $self = shift;
224 21         38 my ( $name ) = @_;
225              
226 21         45 my $builder = Test::Builder->new;
227 21         115 local $Test::Builder::Level = $Test::Builder::Level + 1;
228              
229             $builder->subtest( $name, sub {
230 21     21   10268 my $count = 0;
231 21         25 foreach my $exp ( @{ $self->{expectations} } ) {
  21         38  
232 15         33 $exp->_check( $builder );
233 15         235 $count++;
234             }
235              
236 21 100       47 $builder->ok( 1, "No calls made" ) if !$count;
237 21         102 });
238              
239 21         18615 undef @{ $self->{expectations} };
  21         77  
240              
241             # Only clear the non-indefinite ones
242 21         25 foreach my $method ( keys %{ $self->{whenever} } ) {
  21         59  
243 7         10 my $whenevers = $self->{whenever}{$method};
244              
245 7         12 @$whenevers = grep { $_->{indefinitely} } @$whenevers;
  6         19  
246              
247 7 100       28 @$whenevers or delete $self->{whenever}{$method};
248             }
249             }
250              
251             package
252             Test::ExpectAndCheck::_Expectation;
253              
254 4     4   23 use List::Util qw( all );
  4         6  
  4         2991  
255              
256             =head1 EXPECTATIONS
257              
258             Each value returned by the L method is an "expectation", an object
259             that represents one expected method call, the arguments it should receive, and
260             the return value it should provide.
261              
262             =cut
263              
264             sub new
265             {
266 20     20   30 my $class = shift;
267 20         31 my ( $method, $args, $file, $line ) = @_;
268 20         66 return bless {
269             method => $method,
270             args => $args,
271             file => $file,
272             line => $line,
273             }, $class;
274             }
275              
276             =head2 will_return
277              
278             $exp->will_return( @result );
279              
280             I
281              
282             Sets the result that will be returned by this method call.
283              
284             This method used to be named C, which should be avoided in new code.
285             Uses of the old name will print a deprecation warning.
286              
287             =cut
288              
289             sub will_return
290             {
291 13     13   87 my $self = shift;
292 13         21 my @result = @_;
293              
294 13     16   41 return $self->will_return_using( sub { return @result } );
  16         22  
295             }
296              
297             sub returns
298             {
299 0     0   0 warnings::warnif deprecated => "Calling \$exp->returns() is now deprecated; use ->will_return instead";
300 0         0 return shift->will_return( @_ );
301             }
302              
303             =head2 will_return_using
304              
305             $exp->will_return_using( sub ($args) { ... } );
306              
307             I
308              
309             Sets the result that will be returned, calculated by invoking the code.
310              
311             The code block is invoked at the time that a result is needed. It is invoked
312             with an array reference containing the arguments to the original method call.
313             This is especially useful for expectations created using L.
314              
315             There is no corresponding C, but an exception thrown by this
316             code will be seen by the calling code.
317              
318             =cut
319              
320             sub will_return_using
321             {
322 17     17   21 my $self = shift;
323 17         22 my ( $code ) = @_;
324              
325 17         24 $self->{gen_return} = $code;
326              
327 17         29 return $self;
328             }
329              
330             =head2 will_throw
331              
332             $exp->will_throw( $e );
333              
334             I
335              
336             Sets the exception that will be thrown by this method call.
337              
338             This method used to be named C, which should be avoided in new code.
339              
340             =cut
341              
342             sub will_throw
343             {
344 1     1   1 my $self = shift;
345 1         3 my ( $exception ) = @_;
346              
347 1     1   4 return $self->will_return_using( sub { die $exception } );
  1         6  
348             }
349              
350             sub throws
351             {
352 0     0   0 warnings::warnif deprecated => "Calling \$exp->throws() is now deprecated; use ->will_throw instead";
353 0         0 return shift->will_throw( @_ );
354             }
355              
356             =head2 will_also
357              
358             $exp->will_also( sub { ... } );
359              
360             I
361              
362             Adds extra code which is run when the expected method is called, in addition
363             to generating the result value or exception.
364              
365             When invoked, the code body is invoked in void context with no additional
366             arguments.
367              
368             =cut
369              
370             sub will_also
371             {
372 3     3   5 my $self = shift;
373 3         4 push @{ $self->{also} }, @_;
  3         16  
374              
375 3         7 return $self;
376             }
377              
378             =head2 indefinitely
379              
380             $exp->indefinitely;
381              
382             I
383              
384             On an expectation created using L, this expectation will not be
385             cleared by L, effectively establishing its effects for the
386             entire lifetime of the test script.
387              
388             On an expectation created using L this has no effect; such an
389             expectation will still be cleared as usual.
390              
391             =cut
392              
393             sub indefinitely
394             {
395 1     1   2 my $self = shift;
396              
397 1         2 $self->{indefinitely}++;
398              
399 1         1 return $self;
400             }
401              
402             sub _consume
403             {
404 26     26   30 my $self = shift;
405 26         39 my ( $method, @args ) = @_;
406              
407             $method eq $self->{method} or
408 26 50       46 return 0;
409              
410 26         61 my ( $ok, $stack ) = Test::Deep::cmp_details( \@args, $self->{args} );
411 26 100       35029 unless( $ok ) {
412 3         8 $self->{diag} = Test::Deep::deep_diag( $stack );
413 3         326 return 0;
414             }
415              
416 23         36 $self->{called}++;
417 23         115 return 1;
418             }
419              
420             sub _check
421             {
422 15     15   19 my $self = shift;
423 15         17 my ( $builder ) = @_;
424              
425 15         20 my $method = $self->{method};
426 15         28 $builder->ok( $self->{called}, "->$method(${\ Test::ExpectAndCheck::_stringify_args @{ $self->{args} } })" );
  15         16  
  15         34  
427 15 100       4494 $builder->diag( $self->{diag} ) if defined $self->{diag};
428             }
429              
430             sub _result
431             {
432 23     23   26 my $self = shift;
433 23         33 my @args = @_;
434              
435 23 100       42 if( my $also = $self->{also} ) {
436 3         8 $_->() for @$also;
437             }
438              
439 23         31 my @result;
440 23 100       53 @result = $self->{gen_return}->( \@args ) if $self->{gen_return};
441 22 50       53 return @result if wantarray;
442 22         116 return $result[0];
443             }
444              
445             sub _called
446             {
447 14     14   16 my $self = shift;
448 14         49 return $self->{called};
449             }
450              
451             sub _stringify
452             {
453 1     1   2 my $self = shift;
454 1         2 return "->$self->{method}(${\( Test::ExpectAndCheck::_stringify_args @{ $self->{args} } )}) at $self->{file} line $self->{line}";
  1         2  
  1         3  
455             }
456              
457             package
458             Test::ExpectAndCheck::_Obj;
459              
460             our @CARP_NOT = qw( Test::ExpectAndCheck );
461              
462             sub new
463             {
464 3     3   6 my $class = shift;
465 3         16 my ( $controller ) = @_;
466              
467 3         6 return bless [ $controller ], $class;
468             }
469              
470             sub AUTOLOAD
471             {
472 28     28   5557 my $self = shift;
473 28         142 ( our $AUTOLOAD ) =~ m/::([^:]+)$/;
474 28         54 my $method = $1;
475              
476 28 100       347 return if $method eq "DESTROY";
477              
478 25         55 return $self->[0]->_call( $method, @_ );
479             }
480              
481             =head1 AUTHOR
482              
483             Paul Evans
484              
485             =cut
486              
487             0x55AA;