File Coverage

blib/lib/Test/ExpectAndCheck.pm
Criterion Covered Total %
statement 187 201 93.0
branch 45 60 75.0
condition 15 19 78.9
subroutine 41 43 95.3
pod 5 5 100.0
total 293 328 89.3


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-2026 -- leonerd@leonerd.org.uk
5              
6             package Test::ExpectAndCheck 0.08;
7              
8 6     6   1401306 use v5.14;
  6         25  
9 6     6   41 use warnings;
  6         13  
  6         350  
10              
11 6     6   34 use Carp;
  6         9  
  6         581  
12              
13 6     6   36 use List::Util qw( first );
  6         14  
  6         473  
14 6     6   43 use Scalar::Util qw( blessed );
  6         18  
  6         335  
15              
16 6     6   5410 use Test::Deep ();
  6         117333  
  6         259  
17              
18 6     6   69 use Exporter 'import';
  6         13  
  6         366  
19             our @EXPORT_OK = qw( namedargs );
20              
21 6     6   41 use constant EXPECTATION_CLASS => "Test::ExpectAndCheck::_Expectation";
  6         14  
  6         14783  
22              
23             =head1 NAME
24              
25             C - C-style unit testing with object methods
26              
27             =head1 SYNOPSIS
28              
29             =for highlighter language=perl
30              
31             use Test::More;
32             use Test::ExpectAndCheck;
33              
34             my ( $controller, $mock ) = Test::ExpectAndCheck->create;
35              
36             {
37             $controller->expect( act => 123, 45 )
38             ->will_return( 678 );
39              
40             is( $mock->act( 123, 45 ), 678, '$mock->act returns result' );
41              
42             $controller->check_and_clear( '->act' );
43             }
44              
45             done_testing;
46              
47             =head1 DESCRIPTION
48              
49             This package creates objects that assist in writing unit tests with mocked
50             object instances. Each mock instance will expect to receive a given list of
51             method calls. Each method call is checked that it received the right
52             arguments, and will return a prescribed result. At the end of each test, each
53             object is checked to ensure all the expected methods were called.
54              
55             =head2 Verbose Mode
56              
57             Sometimes when debugging a failing test it can be useful to see a log of which
58             expectations have been called. By setting the C<$VERBOSE> package variable to
59             a true value, extra printing will happen during the test.
60              
61             {
62             local $Test::ExpectAndCheck::VERBOSE = 1;
63              
64             $controller->expect( ... );
65              
66             ...
67             }
68              
69             This is printed directly to C and is intended for temporary debugging
70             during development.
71              
72             =cut
73              
74             our $VERBOSE = 0;
75              
76             =head1 METHODS
77              
78             =cut
79              
80             =head2 create
81              
82             ( $controller, $mock ) = Test::ExpectAndCheck->create( %params );
83              
84             Objects are created in "entangled pairs" by the C method. The first
85             object is called the "controller", and is used by the unit testing script to
86             set up what method calls are to be expected, and what their results shall be.
87             The second object is the "mock", the object to be passed to the code being
88             tested, on which the expected method calls are (hopefully) invoked. It will
89             have whatever interface is implied by the method call expectations.
90              
91             Takes the following optional named parameters:
92              
93             =over 4
94              
95             =item isa
96              
97             isa => [qw( Some Class Names )],
98              
99             I
100              
101             Specifies a list of class names that the mock object will claim to be if
102             interrogated by its C method. This is useful if the code under test will
103             use an C<< ->isa >> check on the passed-in mocking object and therefore it
104             needs to pretend to be of some particular class.
105              
106             Note though that no I method inheritence will take place here. This
107             simply provides the C method. Any actual required behaviour will still
108             have to be created using the L or L methods as usual.
109              
110             =back
111              
112             =cut
113              
114             sub create
115             {
116 5     5 1 1214946 my $class = shift;
117 5         41 my %params = @_;
118              
119 5         33 my $controller = bless {
120             expectations => [],
121             whenever => {},
122             }, $class;
123             my $mock = Test::ExpectAndCheck::_Obj->new( $controller,
124             isa => $params{isa},
125 5         121 );
126              
127 5         29 return ( $controller, $mock );
128             }
129              
130             =head2 expect
131              
132             $exp = $controller->expect( $method, @args );
133              
134             Specifies that the mock will expect to receive a method call of the given
135             name, with the given arguments.
136              
137             The argument values are compared using L. Values can
138             be specified literally, or using any of the "Special Comparisons" defined by
139             L. Additionally, the L function listed below can also
140             appear as the final argument test.
141              
142             The test script can call the L or L methods on the
143             expectation to set what the result of invoking this method will be.
144              
145             =cut
146              
147             sub expect
148             {
149 19     19 1 67810 my $self = shift;
150 19         84 my ( $method, @args ) = @_;
151              
152 19         68 my ( undef, $file, $line ) = caller(1);
153 19 50       164 defined $file or ( undef, $file, $line ) = caller(0);
154              
155 19         56 push @{ $self->{expectations} }, my $exp = $self->EXPECTATION_CLASS->new(
  19         244  
156             $method => [ @args ], $file, $line,
157             );
158              
159 19         132 return $exp;
160             }
161              
162             =head2 whenever
163              
164             $exp = $controller->whenever( $method, @args );
165              
166             I
167              
168             Specifies that the mock might expect to receive method calls of the given name
169             with the given arguments. These expectations are not expired once called, nor
170             do they expect to be called in any particular order. Furthermore it is not a
171             test failure for one of these not to be invoked at all.
172              
173             These expectations do not directly form part of the test assertions checked by
174             the L method, but they may be useful to assist the code
175             under test, such as providing support behaviours that it may rely on but would
176             make the test script too fragile if spelled out in full using a regular
177             C.
178              
179             These expectations are only used as a fallback mechanism, if the next real
180             C-based expectation does not match a method call. Individual special
181             cases can still be set up using C even though a C exists
182             that might also match it.
183              
184             As with L, the argument values are compared using C, and
185             results can be set with L or L.
186              
187             =cut
188              
189             sub whenever
190             {
191 5     5 1 14961 my $self = shift;
192 5         18 my ( $method, @args ) = @_;
193              
194 5         16 my ( undef, $file, $line ) = caller(1);
195 5 50       43 defined $file or ( undef $file, $line ) = caller(0);
196              
197 5         13 push @{ $self->{whenever}{$method} }, my $exp = $self->EXPECTATION_CLASS->new(
  5         63  
198             $method => [ @args ], $file, $line,
199             );
200              
201 5         39 return $exp;
202             }
203              
204             sub _stringify
205             {
206 63     63   145 my ( $v, $autoquote ) = @_;
207 63 50 66     766 if( !defined $v ) {
    50 66        
    100 66        
    100          
    100          
    50          
208 0 0       0 return "+undef" if $autoquote;
209 0         0 return "undef";
210             }
211             elsif( blessed $v and $v->isa( "Test::Deep::Ignore" ) ) {
212 0         0 return "ignore()";
213             }
214             elsif( blessed $v and $v->isa( "Test::ExpectAndCheck::_NamedArgsChecker" ) ) {
215 6         18 my %args = %{ $v->{val} };
  6         33  
216             return "namedargs(" .
217 6         47 join( ", ", map { sprintf "%s => %s", _stringify($_, 1), _stringify($args{$_}) } sort keys %args )
  12         32  
218             . ")";
219             }
220             elsif( $v =~ m/^-?[0-9]+$/ ) {
221 18         1245 return sprintf "%d", $v;
222             }
223             elsif( $autoquote and $v =~ m/^[[:alpha:]_][[:alnum:]_]*$/ ) {
224 12         40 return $v;
225             }
226             elsif( $v =~ m/^[\x20-\x7E]*\z/ ) {
227 27         85 $v =~ s/([\\'])/\\$1/g;
228 27         156 return qq('$v');
229             }
230             else {
231 0 0       0 if( $v =~ m/[^\n\r\x20-\x7E]/ ) {
232             # string contains something non-printable; just hexdump it all
233 0         0 $v =~ s{(.)}{sprintf "\\x%02X", ord $1}gse;
  0         0  
234             }
235             else {
236 0         0 $v =~ s/([\\'\$\@])/\\$1/g;
237 0         0 $v =~ s{\n}{\\n}g;
238 0         0 $v =~ s{\r}{\\r}g;
239             }
240 0         0 return qq("$v");
241             }
242             }
243              
244             sub _stringify_args
245             {
246 26     26   148 join ", ", map { _stringify $_ } @_;
  39         95  
247             }
248              
249             sub _call
250             {
251 29     29   58 my $self = shift;
252 29         60 my $method = shift;
253 29         121 my $args = \@_;
254              
255 29         60 my $e;
256 29 100 100 18   143 $e = first { !$_->_called } @{ $self->{expectations} } and
  18         77  
  29         194  
257             $e->_consume( $method, @$args ) and
258             return $e->_result( $args );
259              
260 14 100   12   95 if( my $wh = first { $_->_consume( $method, @$args ) } @{ $self->{whenever}{$method} } ) {
  12         41  
  14         77  
261 10         34 return $wh->_result( $args );
262             }
263              
264 4         20 my $message = Carp::shortmess( "Unexpected call to ->$method(${\ _stringify_args @$args })" );
  4         21  
265 4 100       36 $message .= "... while expecting " . $e->_stringify if $e;
266 4 100       18 $message .= "... after all expectations done" if !$e;
267 4         59 die "$message.\n";
268             }
269              
270             =head2 check_and_clear
271              
272             $controller->check_and_clear( $name );
273              
274             Checks that by now, every expected method has been called, and emits a new
275             test output line via L. Regardless, the expectations are also
276             cleared out ready for the start of the next test.
277              
278             =cut
279              
280             sub check_and_clear
281             {
282 25     25 1 6639 my $self = shift;
283 25         77 my ( $name ) = @_;
284              
285 25         137 my $builder = Test::Builder->new;
286 25         216 local $Test::Builder::Level = $Test::Builder::Level + 1;
287              
288             $builder->subtest( $name, sub {
289 25     25   38260 my $count = 0;
290 25         53 foreach my $exp ( @{ $self->{expectations} } ) {
  25         88  
291 19         93 $exp->_check( $builder );
292 19         2265 $count++;
293             }
294              
295 25 100       110 $builder->ok( 1, "No calls made" ) if !$count;
296 25         254 });
297              
298 25         64622 undef @{ $self->{expectations} };
  25         256  
299              
300             # Only clear the non-indefinite ones
301 25         77 foreach my $method ( keys %{ $self->{whenever} } ) {
  25         144  
302 9         26 my $whenevers = $self->{whenever}{$method};
303              
304 9         29 @$whenevers = grep { $_->{indefinitely} } @$whenevers;
  6         47  
305              
306 9 100       70 @$whenevers or delete $self->{whenever}{$method};
307             }
308             }
309              
310             =head1 FUNCTIONS
311              
312             =head2 namedargs
313              
314             $cmp = namedargs(name => $val, ...)
315              
316             I
317              
318             This exportable function may be used as the final argument to an L
319             or L expectation, to indicate that all of the remaining arguments
320             passed at that position should be treated like named parameters in a list of
321             key/value pairs. This makes then insensitive to the order that the values are
322             passed by the caller.
323              
324             Each value given can be a literal value or a special comparison from
325             C.
326              
327             For example, this simple expectation will fail 50% of the time due to hash
328             order randomisation:
329              
330             $controller->expect( m => x => "X", y => "Y" );
331              
332             my %args = ( x => "X", y => "Y" );
333             $puppet->m( %args );
334              
335             This is solved by using the C function.
336              
337             use Test::ExpectAndCheck 'namedargs';
338              
339             $controller->expect( m => namedargs(x => "X", y => "Y") );
340              
341             Additionally, positional arguments may appear before this call.
342              
343             $controller->expect( n => 1, 2, namedargs(x => "X", y => "Y") );
344             $puppet->n( $one, $two, %args );
345              
346             =cut
347              
348             ## named arg support
349             sub namedargs
350             {
351 4     4 1 18022 my %args = @_;
352 4         47 return Test::ExpectAndCheck::_NamedArgsChecker->new( \%args );
353             }
354              
355             package Test::ExpectAndCheck::_FirstAndFinalChecker
356             {
357 6     6   61 use base 'Test::Deep::Cmp';
  6         26  
  6         3627  
358              
359             sub init
360             {
361 4     4   45 my ( $self, @vals ) = @_;
362 4         34 my $final = pop @vals;
363              
364 4         56 $self->{first} = \@vals;
365 4         16 $self->{final} = $final;
366             }
367              
368             sub descend
369             {
370 4     4   1096 my ( $self, $got ) = @_;
371 4 50       19 return 0 unless ref $got eq "ARRAY";
372 4         19 my @got = @$got;
373              
374 4         17 foreach my $exp1 ( @{ $self->{first} } ) {
  4         15  
375 8 100       115 return 0 unless Test::Deep::descend( shift @got, $exp1 );
376             }
377              
378 3         42 return Test::Deep::descend( \@got, $self->{final} );
379             }
380             }
381              
382             package Test::ExpectAndCheck::_NamedArgsChecker
383             {
384 6     6   7122 use base 'Test::Deep::Hash';
  6         14  
  6         3132  
385              
386             sub descend
387             {
388 3     3   463 my ( $self, $got ) = @_;
389 3 50       13 return 0 unless ref $got eq "ARRAY";
390 3         14 my %got = @$got;
391              
392 3         40 return $self->SUPER::descend( \%got );
393             }
394             }
395              
396             package
397             Test::ExpectAndCheck::_Expectation;
398              
399 6     6   12135 use List::Util qw( all );
  6         11  
  6         9479  
400              
401             =head1 EXPECTATIONS
402              
403             Each value returned by the L method is an "expectation", an object
404             that represents one expected method call, the arguments it should receive, and
405             the return value it should provide.
406              
407             =cut
408              
409             sub new
410             {
411 24     24   61 my $class = shift;
412 24         89 my ( $method, $args, $file, $line ) = @_;
413              
414 24         50 my $argcheck;
415 24 100 50     194 if( @$args and ( ref $args->[-1] // "" ) eq "Test::ExpectAndCheck::_NamedArgsChecker" ) {
      100        
416 4         31 $argcheck = Test::ExpectAndCheck::_FirstAndFinalChecker->new( @$args );
417             }
418             else {
419 20         84 $argcheck = Test::Deep::array( $args );
420             }
421              
422 24         4671 return bless {
423             method => $method,
424             args => $args,
425             argcheck => $argcheck,
426             file => $file,
427             line => $line,
428             }, $class;
429             }
430              
431             =head2 will_return
432              
433             $exp->will_return( @result );
434              
435             I
436              
437             Sets the result that will be returned by this method call.
438              
439             This method used to be named C, which should be avoided in new code.
440             Uses of the old name will print a deprecation warning.
441              
442             =cut
443              
444             sub will_return
445             {
446 13     13   195 my $self = shift;
447 13         41 my @result = @_;
448              
449 13     16   92 return $self->will_return_using( sub { return @result } );
  16         83  
450             }
451              
452             sub returns
453             {
454 0     0   0 warnings::warnif deprecated => "Calling \$exp->returns() is now deprecated; use ->will_return instead";
455 0         0 return shift->will_return( @_ );
456             }
457              
458             =head2 will_return_using
459              
460             $exp->will_return_using( sub ($args) { ... } );
461              
462             I
463              
464             Sets the result that will be returned, calculated by invoking the code.
465              
466             The code block is invoked at the time that a result is needed. It is invoked
467             with an array reference containing the arguments to the original method call.
468             This is especially useful for expectations created using L.
469              
470             I the code block is passed a reference to the caller's
471             actual arguments array, and therefore can modify values in it if required -
472             e.g. when trying to mock functions such as C or C which
473             modify lvalues passed in as arguments.
474              
475             There is no corresponding C, but an exception thrown by this
476             code will be seen by the calling code.
477              
478             =cut
479              
480             sub will_return_using
481             {
482 17     17   53 my $self = shift;
483 17         90 my ( $code ) = @_;
484              
485 17         50 $self->{gen_return} = $code;
486              
487 17         56 return $self;
488             }
489              
490             =head2 will_throw
491              
492             $exp->will_throw( $e );
493              
494             I
495              
496             Sets the exception that will be thrown by this method call.
497              
498             This method used to be named C, which should be avoided in new code.
499              
500             =cut
501              
502             sub will_throw
503             {
504 1     1   4 my $self = shift;
505 1         4 my ( $exception ) = @_;
506              
507 1     1   9 return $self->will_return_using( sub { die $exception } );
  1         15  
508             }
509              
510             sub throws
511             {
512 0     0   0 warnings::warnif deprecated => "Calling \$exp->throws() is now deprecated; use ->will_throw instead";
513 0         0 return shift->will_throw( @_ );
514             }
515              
516             =head2 will_also
517              
518             $exp->will_also( sub { ... } );
519              
520             I
521              
522             Adds extra code which is run when the expected method is called, in addition
523             to generating the result value or exception.
524              
525             When invoked, the code body is invoked in void context with no additional
526             arguments.
527              
528             =cut
529              
530             sub will_also
531             {
532 3     3   9 my $self = shift;
533 3         8 push @{ $self->{also} }, @_;
  3         12  
534              
535 3         9 return $self;
536             }
537              
538             =head2 indefinitely
539              
540             $exp->indefinitely;
541              
542             I
543              
544             On an expectation created using L, this expectation will not be
545             cleared by L, effectively establishing its effects for the
546             entire lifetime of the test script.
547              
548             On an expectation created using L this has no effect; such an
549             expectation will still be cleared as usual.
550              
551             =cut
552              
553             sub indefinitely
554             {
555 1     1   3 my $self = shift;
556              
557 1         3 $self->{indefinitely}++;
558              
559 1         3 return $self;
560             }
561              
562             sub _consume
563             {
564 30     30   68 my $self = shift;
565 30         98 my ( $method, @args ) = @_;
566              
567             $method eq $self->{method} or
568 30 50       114 return 0;
569              
570 30         181 my ( $ok, $stack ) = Test::Deep::cmp_details( \@args, $self->{argcheck} );
571 30 100       74276 unless( $ok ) {
572 5         21 $self->{diag} = Test::Deep::deep_diag( $stack );
573 5         1011 return 0;
574             }
575              
576 25 50       98 print STDERR "[Test::ExpectAndCheck] called " . $self->_stringify . "\n"
577             if $VERBOSE;
578              
579 25         77 $self->{called}++;
580 25         238 return 1;
581             }
582              
583             sub _check
584             {
585 19     19   135 my $self = shift;
586 19         46 my ( $builder ) = @_;
587              
588 19         87 my $method = $self->{method};
589 19         58 $builder->ok( $self->{called}, "->$method(${\ Test::ExpectAndCheck::_stringify_args @{ $self->{args} } })" );
  19         35  
  19         83  
590 19 100       14667 $builder->diag( $self->{diag} ) if defined $self->{diag};
591             }
592              
593             sub _result
594             {
595 25     25   53 my $self = shift;
596 25         112 my ( $args ) = @_;
597              
598 25 100       88 if( my $also = $self->{also} ) {
599 3         17 $_->() for @$also;
600             }
601              
602 25         52 my @result;
603 25 100       114 @result = $self->{gen_return}->( $args ) if $self->{gen_return};
604 24 50       1018 return @result if wantarray;
605 24         287 return $result[0];
606             }
607              
608             sub _called
609             {
610 18     18   64 my $self = shift;
611 18         164 return $self->{called};
612             }
613              
614             sub _stringify
615             {
616 3     3   7 my $self = shift;
617 3         10 return "->$self->{method}(${\( Test::ExpectAndCheck::_stringify_args @{ $self->{args} } )}) at $self->{file} line $self->{line}";
  3         9  
  3         12  
618             }
619              
620             package
621             Test::ExpectAndCheck::_Obj;
622              
623             our @CARP_NOT = qw( Test::ExpectAndCheck );
624              
625             sub new
626             {
627 5     5   24 my $class = shift;
628 5         24 my ( $controller, %params ) = @_;
629              
630 5 100       26 my $isa = $params{isa} ? [ @{$params{isa}} ] : undef;
  1         2  
631              
632 5         77 return bless [ $controller, $isa ], $class;
633             }
634              
635             sub isa
636             {
637 2     2   6 my $self = shift;
638 2         3 my ( $pkg ) = @_;
639              
640 2 50       9 if( my $isa = $self->[1] ) {
641 2   100     11 $pkg eq $_ and return 1 for @$isa;
642             }
643              
644 1         9 return $self->SUPER::isa( $pkg );
645             }
646              
647             sub AUTOLOAD
648             {
649 34     34   17756 my $self = shift;
650 34         336 ( our $AUTOLOAD ) =~ m/::([^:]+)$/;
651 34         119 my $method = $1;
652              
653 34 100       802 return if $method eq "DESTROY";
654              
655 29         148 return $self->[0]->_call( $method, @_ );
656             }
657              
658             =head1 AUTHOR
659              
660             Paul Evans
661              
662             =cut
663              
664             0x55AA;