1package Test2::Mock; 2use strict; 3use warnings; 4 5our $VERSION = '0.000162'; 6 7use Carp qw/croak confess/; 8our @CARP_NOT = (__PACKAGE__); 9 10use Scalar::Util qw/weaken reftype blessed set_prototype/; 11use Test2::Util qw/pkg_to_file/; 12use Test2::Util::Stash qw/parse_symbol slot_to_sig get_symbol get_stash purge_symbol/; 13use Test2::Util::Sub qw/gen_accessor gen_reader gen_writer/; 14 15sub new; # Prevent hashbase from giving us 'new'; 16use Test2::Util::HashBase qw/class parent child _purge_on_destroy _blocked_load _symbols _track sub_tracking call_tracking/; 17 18sub new { 19 my $class = shift; 20 21 croak "Called new() on a blessed instance, did you mean to call \$control->class->new()?" 22 if blessed($class); 23 24 my $self = bless({}, $class); 25 26 $self->{+SUB_TRACKING} ||= {}; 27 $self->{+CALL_TRACKING} ||= []; 28 29 my @sets; 30 while (my $arg = shift @_) { 31 my $val = shift @_; 32 33 if ($class->can(uc($arg))) { 34 $self->{$arg} = $val; 35 next; 36 } 37 38 push @sets => [$arg, $val]; 39 } 40 41 croak "The 'class' field is required" 42 unless $self->{+CLASS}; 43 44 for my $set (@sets) { 45 my ($meth, $val) = @$set; 46 my $type = reftype($val); 47 48 confess "'$meth' is not a valid constructor argument for $class" 49 unless $self->can($meth); 50 51 if (!$type) { 52 $self->$meth($val); 53 } 54 elsif($type eq 'HASH') { 55 $self->$meth(%$val); 56 } 57 elsif($type eq 'ARRAY') { 58 $self->$meth(@$val); 59 } 60 else { 61 croak "'$val' is not a valid argument for '$meth'" 62 } 63 } 64 65 return $self; 66} 67 68sub _check { 69 return unless $_[0]->{+CHILD}; 70 croak "There is an active child controller, cannot proceed"; 71} 72 73sub purge_on_destroy { 74 my $self = shift; 75 ($self->{+_PURGE_ON_DESTROY}) = @_ if @_; 76 return $self->{+_PURGE_ON_DESTROY}; 77} 78 79sub stash { 80 my $self = shift; 81 get_stash($self->{+CLASS}); 82} 83 84sub file { 85 my $self = shift; 86 my $file = $self->class; 87 return pkg_to_file($self->class); 88} 89 90sub block_load { 91 my $self = shift; 92 $self->_check(); 93 94 my $file = $self->file; 95 96 croak "Cannot block the loading of module '" . $self->class . "', already loaded in file $INC{$file}" 97 if $INC{$file}; 98 99 $INC{$file} = __FILE__; 100 101 $self->{+_BLOCKED_LOAD} = 1; 102} 103 104my %NEW = ( 105 hash => sub { 106 my ($class, %params) = @_; 107 return bless \%params, $class; 108 }, 109 array => sub { 110 my ($class, @params) = @_; 111 return bless \@params, $class; 112 }, 113 ref => sub { 114 my ($class, $params) = @_; 115 return bless $params, $class; 116 }, 117 ref_copy => sub { 118 my ($class, $params) = @_; 119 my $type = reftype($params); 120 121 return bless {%$params}, $class 122 if $type eq 'HASH'; 123 124 return bless [@$params], $class 125 if $type eq 'ARRAY'; 126 127 croak "Not sure how to construct an '$class' from '$params'"; 128 }, 129); 130 131sub override_constructor { 132 my $self = shift; 133 my ($name, $type) = @_; 134 $self->_check(); 135 136 my $sub = $NEW{$type} 137 || croak "'$type' is not a known constructor type"; 138 139 $self->override($name => $sub); 140} 141 142sub add_constructor { 143 my $self = shift; 144 my ($name, $type) = @_; 145 $self->_check(); 146 147 my $sub = $NEW{$type} 148 || croak "'$type' is not a known constructor type"; 149 150 $self->add($name => $sub); 151} 152 153sub autoload { 154 my $self = shift; 155 $self->_check(); 156 my $class = $self->class; 157 my $stash = $self->stash; 158 159 croak "Class '$class' already has an AUTOLOAD" 160 if $stash->{AUTOLOAD} && *{$stash->{AUTOLOAD}}{CODE}; 161 croak "Class '$class' already has an can" 162 if $stash->{can} && *{$stash->{can}}{CODE}; 163 164 # Weaken this reference so that AUTOLOAD does not prevent its own 165 # destruction. 166 weaken(my $c = $self); 167 168 my ($file, $line) = (__FILE__, __LINE__ + 3); 169 my $autoload = eval <<EOT || die "Failed generating AUTOLOAD sub: $@"; 170package $class; 171#line $line "$file (Generated AUTOLOAD)" 172our \$AUTOLOAD; 173 sub { 174 my (\$self) = \@_; 175 my (\$pkg, \$name) = (\$AUTOLOAD =~ m/^(.*)::([^:]+)\$/g); 176 \$AUTOLOAD = undef; 177 178 return if \$name eq 'DESTROY'; 179 my \$sub = sub { 180 my \$self = shift; 181 (\$self->{\$name}) = \@_ if \@_; 182 return \$self->{\$name}; 183 }; 184 185 \$c->add(\$name => \$sub); 186 187 if (\$c->{_track}) { 188 my \$call = {sub_name => \$name, sub_ref => \$sub, args => [\@_]}; 189 push \@{\$c->{sub_tracking}->{\$name}} => \$call; 190 push \@{\$c->{call_tracking}} => \$call; 191 } 192 193 goto &\$sub; 194 } 195EOT 196 197 $line = __LINE__ + 3; 198 my $can = eval <<EOT || die "Failed generating can method: $@"; 199package $class; 200#line $line "$file (Generated can)" 201use Scalar::Util 'reftype'; 202 sub { 203 my (\$self, \$meth) = \@_; 204 if (\$self->SUPER::can(\$meth)) { 205 return \$self->SUPER::can(\$meth); 206 } 207 elsif (ref \$self && reftype \$self eq 'HASH' && exists \$self->{\$meth}) { 208 return sub { shift->\$meth(\@_) }; 209 } 210 return undef; 211 } 212EOT 213 214 { 215 local $self->{+_TRACK} = 0; 216 $self->add(AUTOLOAD => $autoload); 217 $self->add(can => $can); 218 } 219} 220 221sub before { 222 my $self = shift; 223 my ($name, $sub) = @_; 224 $self->_check(); 225 my $orig = $self->current($name, required => 1); 226 $self->_inject({}, $name => set_prototype(sub { $sub->(@_); $orig->(@_) }, prototype $sub)); 227} 228 229sub after { 230 my $self = shift; 231 my ($name, $sub) = @_; 232 $self->_check(); 233 my $orig = $self->current($name, required => 1); 234 $self->_inject( 235 {}, 236 $name => set_prototype( 237 sub { 238 my @out; 239 240 my $want = wantarray; 241 242 if ($want) { 243 @out = $orig->(@_); 244 } 245 elsif (defined $want) { 246 $out[0] = $orig->(@_); 247 } 248 else { 249 $orig->(@_); 250 } 251 252 $sub->(@_); 253 254 return @out if $want; 255 return $out[0] if defined $want; 256 return; 257 }, 258 prototype $sub, 259 ) 260 ); 261} 262 263sub around { 264 my $self = shift; 265 my ($name, $sub) = @_; 266 $self->_check(); 267 my $orig = $self->current($name, required => 1); 268 $self->_inject({}, $name => set_prototype(sub { $sub->($orig, @_) }, prototype $sub)); 269} 270 271sub add { 272 my $self = shift; 273 $self->_check(); 274 $self->_inject({add => 1}, @_); 275} 276 277sub override { 278 my $self = shift; 279 $self->_check(); 280 $self->_inject({}, @_); 281} 282 283sub set { 284 my $self = shift; 285 $self->_check(); 286 $self->_inject({set => 1}, @_); 287} 288 289sub current { 290 my $self = shift; 291 my ($sym, %params) = @_; 292 293 my $out = get_symbol($sym, $self->{+CLASS}); 294 return $out unless $params{required}; 295 confess "Attempt to modify a sub that does not exist '$self->{+CLASS}\::$sym' (Mock operates on packages, not classes, are you looking for a symbol in a parent class?)" 296 unless $out; 297 return $out; 298} 299 300sub orig { 301 my $self = shift; 302 my ($sym) = @_; 303 304 $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/; 305 306 my $syms = $self->{+_SYMBOLS} 307 or croak "No symbols have been mocked yet"; 308 309 my $ref = $syms->{$sym}; 310 311 croak "Symbol '$sym' is not mocked" 312 unless $ref && @$ref; 313 314 my ($orig) = @$ref; 315 316 return $orig; 317} 318 319sub track { 320 my $self = shift; 321 322 ($self->{+_TRACK}) = @_ if @_; 323 324 return $self->{+_TRACK}; 325} 326 327sub clear_call_tracking { @{shift->{+CALL_TRACKING}} = () } 328 329sub clear_sub_tracking { 330 my $self = shift; 331 332 unless (@_) { 333 %{$self->{+SUB_TRACKING}} = (); 334 return; 335 } 336 337 for my $item (@_) { 338 delete $self->{+SUB_TRACKING}->{$item}; 339 } 340 341 return; 342} 343 344sub _parse_inject { 345 my $self = shift; 346 my ($param, $arg) = @_; 347 348 if ($param =~ m/^-(.*)$/) { 349 my $sym = $1; 350 my $sig = slot_to_sig(reftype($arg)); 351 my $ref = $arg; 352 return ($sig, $sym, $ref); 353 } 354 355 return ('&', $param, $arg) 356 if ref($arg) && reftype($arg) eq 'CODE'; 357 358 my ($is, $field, $val); 359 360 if(defined($arg) && !ref($arg) && $arg =~ m/^(rw|ro|wo)$/) { 361 $is = $arg; 362 $field = $param; 363 } 364 elsif (!ref($arg)) { 365 $val = $arg; 366 $is = 'val'; 367 } 368 elsif (reftype($arg) eq 'HASH') { 369 $field = delete $arg->{field} || $param; 370 371 $val = delete $arg->{val}; 372 $is = delete $arg->{is}; 373 374 croak "Cannot specify 'is' and 'val' together" if $val && $is; 375 376 $is ||= $val ? 'val' : 'rw'; 377 378 croak "The following keys are not valid when defining a mocked sub with a hashref: " . join(", " => keys %$arg) 379 if keys %$arg; 380 } 381 else { 382 confess "'$arg' is not a valid argument when defining a mocked sub"; 383 } 384 385 my $sub; 386 if ($is eq 'rw') { 387 $sub = gen_accessor($field); 388 } 389 elsif ($is eq 'ro') { 390 $sub = gen_reader($field); 391 } 392 elsif ($is eq 'wo') { 393 $sub = gen_writer($field); 394 } 395 else { # val 396 $sub = sub { $val }; 397 } 398 399 return ('&', $param, $sub); 400} 401 402sub _inject { 403 my $self = shift; 404 my ($params, @pairs) = @_; 405 406 my $add = $params->{add}; 407 my $set = $params->{set}; 408 409 my $class = $self->{+CLASS}; 410 411 $self->{+_SYMBOLS} ||= {}; 412 my $syms = $self->{+_SYMBOLS}; 413 414 while (my $param = shift @pairs) { 415 my $arg = shift @pairs; 416 my ($sig, $sym, $ref) = $self->_parse_inject($param, $arg); 417 my $orig = $self->current("$sig$sym"); 418 419 croak "Cannot override '$sig$class\::$sym', symbol is not already defined" 420 unless $orig || $add || $set || ($sig eq '&' && $class->can($sym)); 421 422 # Cannot be too sure about scalars in globs 423 croak "Cannot add '$sig$class\::$sym', symbol is already defined" 424 if $add && $orig 425 && (reftype($orig) ne 'SCALAR' || defined($$orig)); 426 427 $syms->{"$sig$sym"} ||= []; 428 push @{$syms->{"$sig$sym"}} => $orig; # Might be undef, thats expected 429 430 if ($self->{+_TRACK} && $sig eq '&') { 431 my $sub_tracker = $self->{+SUB_TRACKING}; 432 my $call_tracker = $self->{+CALL_TRACKING}; 433 my $sub = $ref; 434 $ref = sub { 435 my $call = {sub_name => $sym, sub_ref => $sub, args => [@_]}; 436 push @{$sub_tracker->{$param}} => $call; 437 push @$call_tracker => $call; 438 goto &$sub; 439 }; 440 } 441 442 no strict 'refs'; 443 no warnings 'redefine'; 444 *{"$class\::$sym"} = $ref; 445 } 446 447 return; 448} 449 450sub _set_or_unset { 451 my $self = shift; 452 my ($symbol, $set) = @_; 453 454 my $class = $self->{+CLASS}; 455 456 return purge_symbol($symbol, $class) 457 unless $set; 458 459 my $sym = parse_symbol($symbol, $class); 460 no strict 'refs'; 461 no warnings 'redefine'; 462 *{"$class\::$sym->{name}"} = $set; 463} 464 465sub restore { 466 my $self = shift; 467 my ($sym) = @_; 468 $self->_check(); 469 470 $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/; 471 472 my $syms = $self->{+_SYMBOLS} 473 or croak "No symbols are mocked"; 474 475 my $ref = $syms->{$sym}; 476 477 croak "Symbol '$sym' is not mocked" 478 unless $ref && @$ref; 479 480 my $old = pop @$ref; 481 delete $syms->{$sym} unless @$ref; 482 483 return $self->_set_or_unset($sym, $old); 484} 485 486sub reset { 487 my $self = shift; 488 my ($sym) = @_; 489 $self->_check(); 490 491 $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/; 492 493 my $syms = $self->{+_SYMBOLS} 494 or croak "No symbols are mocked"; 495 496 my $ref = delete $syms->{$sym}; 497 498 croak "Symbol '$sym' is not mocked" 499 unless $ref && @$ref; 500 501 my ($old) = @$ref; 502 503 return $self->_set_or_unset($sym, $old); 504} 505 506sub reset_all { 507 my $self = shift; 508 $self->_check(); 509 510 my $syms = $self->{+_SYMBOLS} || return; 511 512 $self->reset($_) for keys %$syms; 513 514 delete $self->{+_SYMBOLS}; 515} 516 517sub _purge { 518 my $self = shift; 519 my $stash = $self->stash; 520 delete $stash->{$_} for keys %$stash; 521} 522 523sub DESTROY { 524 my $self = shift; 525 526 delete $self->{+CHILD}; 527 $self->reset_all if $self->{+_SYMBOLS}; 528 529 delete $INC{$self->file} if $self->{+_BLOCKED_LOAD}; 530 531 $self->_purge if $self->{+_PURGE_ON_DESTROY}; 532} 533 5341; 535 536__END__ 537 538=pod 539 540=encoding UTF-8 541 542=head1 NAME 543 544Test2::Mock - Module for managing mocked classes and instances. 545 546=head1 DESCRIPTION 547 548This module lets you add and override methods for any package temporarily. When 549the instance is destroyed it will restore the package to its original state. 550 551=head1 SYNOPSIS 552 553 use Test2::Mock; 554 use MyClass; 555 556 my $mock = Test2::Mock->new( 557 track => $BOOL, # enable call tracking if desired 558 class => 'MyClass', 559 override => [ 560 name => sub { 'fred' }, 561 ... 562 ], 563 add => [ 564 is_mocked => sub { 1 } 565 ... 566 ], 567 ... 568 ); 569 570 # Unmock the 'name' sub 571 $mock->restore('name'); 572 573 ... 574 575 $mock = undef; # Will remove all the mocking 576 577=head1 CONSTRUCTION 578 579=head1 METHODS 580 581=over 4 582 583=item $mock = Test2::Mock->new(class => $CLASS, ...) 584 585This will create a new instance of L<Test2::Mock> that manages mocking 586for the specified C<$CLASS>. 587 588Any C<Test2::Mock> method can be used as a constructor argument, each 589should be followed by an arrayref of arguments to be used within the method. For 590instance the C<add()> method: 591 592 my $mock = Test2::Mock->new( 593 class => 'AClass', 594 add => [foo => sub { 'foo' }], 595 ); 596 597is identical to this: 598 599 my $mock = Test2::Mock->new( 600 class => 'AClass', 601 ); 602 $mock->add(foo => sub { 'foo' }); 603 604=item $mock->track($bool) 605 606Turn tracking on or off. Any sub added/overridden/set when tracking is on will 607log every call in a hash retrievable via C<< $mock->tracking >>. Changing the 608tracking toggle will not affect subs already altered, but will affect any 609additional alterations. 610 611=item $hashref = $mock->sub_tracking 612 613The tracking data looks like this: 614 615 { 616 sub_name => [ 617 {sub_name => $sub_name, sub_ref => $mock_subref, args => [... copy of @_ from the call ... ]}, 618 ..., 619 ..., 620 ], 621 } 622 623Unlike call_tracking, this lists all calls by sub, so you can choose to only 624look at the sub specific calls. 625 626B<Please note:> The hashref items with the subname and args are shared with 627call_tracking, modifying one modifies the other, so copy first! 628 629=item $arrayref = $mock->call_tracking 630 631The tracking data looks like this: 632 633 [ 634 {sub_name => $sub_name, sub_ref => $mock_subref, args => [... copy of @_ from the call ... ]}, 635 ..., 636 ..., 637 ] 638 639Unlike sub_tracking this lists all calls to any mocked sub, in the order they 640were called. To filter by sub use sub_tracking. 641 642B<Please note:> The hashref items with the subname and args are shared with 643sub_tracking, modifying one modifies the other, so copy first! 644 645=item $mock->clear_sub_tracking() 646 647=item $mock->clear_sub_tracking(\@subnames) 648 649Clear tracking data. With no arguments ALL tracking data is cleared. When 650arguments are provided then only those specific keys will be cleared. 651 652=item $mock->clear_call_tracking() 653 654Clear all items from call_tracking. 655 656=item $mock->add('symbol' => ..., 'symbol2' => ...) 657 658=item $mock->override('symbol1' => ..., 'symbol2' => ...) 659 660=item $mock->set('symbol1' => ..., 'symbol2' => ...) 661 662C<add()> and C<override()> are the primary ways to add/modify methods for a 663class. Both accept the exact same type of arguments. The difference is that 664C<override> will fail unless the symbol you are overriding already exists, 665C<add> on the other hand will fail if the symbol does already exist. 666 667C<set()> was more recently added for cases where you may not know if the sub 668already exists. These cases are rare, and set should be avoided (think of it 669like 'no strict'). However there are valid use cases, so it was added. 670 671B<Note:> Think of override as a push operation. If you call override on the 672same symbol multiple times it will track that. You can use C<restore()> as a 673pop operation to go back to the previous mock. C<reset> can be used to remove 674all the mocking for a symbol. 675 676Arguments must be a symbol name, with optional sigil, followed by a new 677specification of the symbol. If no sigil is specified then '&' (sub) is 678assumed. A simple example of overriding a sub: 679 680 $mock->override(foo => sub { 'overridden foo' }); 681 my $val = $class->foo; # Runs our override 682 # $val is now set to 'overridden foo' 683 684You can also simply provide a value and it will be wrapped in a sub for you: 685 686 $mock->override( foo => 'foo' ); 687 688The example above will generate a sub that always returns the string 'foo'. 689 690There are three *special* values that can be used to generate accessors: 691 692 $mock->add( 693 name => 'rw', # Generates a read/write accessor 694 age => 'ro', # Generates a read only accessor 695 size => 'wo', # Generates a write only accessor 696 ); 697 698If you want to have a sub that actually returns one of the three special strings, or 699that returns a coderef, you can use a hashref as the spec: 700 701 my $ref = sub { 'my sub' }; 702 $mock->add( 703 rw_string => { val => 'rw' }, 704 ro_string => { val => 'ro' }, 705 wo_string => { val => 'wo' }, 706 coderef => { val => $ref }, # the coderef method returns $ref each time 707 ); 708 709You can also override/add other symbol types, such as hash: 710 711 package Foo; 712 ... 713 714 $mock->add('%foo' => {a => 1}); 715 716 print $Foo::foo{a}; # prints '1' 717 718You can also tell mock to deduce the symbol type for the add/override from the 719reference, rules are similar to glob assignments: 720 721 $mock->add( 722 -foo => sub { 'foo' }, # Adds the &foo sub to the package 723 -foo => { foo => 1 }, # Adds the %foo hash to the package 724 -foo => [ 'f', 'o', 'o' ], # Adds the @foo array to the package 725 -foo => \"foo", # Adds the $foo scalar to the package 726 ); 727 728=item $mock->restore($SYMBOL) 729 730Restore the symbol to what it was before the last override. If the symbol was 731recently added this will remove it. If the symbol has been overridden multiple 732times this will ONLY restore it to the previous state. Think of C<override> as a 733push operation, and C<restore> as the pop operation. 734 735=item $mock->reset($SYMBOL) 736 737Remove all mocking of the symbol and restore the original symbol. If the symbol 738was initially added then it will be completely removed. 739 740=item $mock->orig($SYMBOL) 741 742This will return the original symbol, before any mocking. For symbols that were 743added this will return undef. 744 745=item $mock->current($SYMBOL) 746 747This will return the current symbol. 748 749=item $mock->reset_all 750 751Remove all added symbols, and restore all overridden symbols to their originals. 752 753=item $mock->add_constructor($NAME => $TYPE) 754 755=item $mock->override_constructor($NAME => $TYPE) 756 757This can be used to inject constructors. The first argument should be the name 758of the constructor. The second argument specifies the constructor type. 759 760The C<hash> type is the most common, all arguments are used to create a new 761hash that is blessed. 762 763 hash => sub { 764 my ($class, %params) = @_; 765 return bless \%params, $class; 766 }; 767 768The C<array> type is similar to the hash type, but accepts a list instead of 769key/value pairs: 770 771 array => sub { 772 my ($class, @params) = @_; 773 return bless \@params, $class; 774 }; 775 776The C<ref> type takes a reference and blesses it. This will modify your 777original input argument. 778 779 ref => sub { 780 my ($class, $params) = @_; 781 return bless $params, $class; 782 }; 783 784The C<ref_copy> type will copy your reference and bless the copy: 785 786 ref_copy => sub { 787 my ($class, $params) = @_; 788 my $type = reftype($params); 789 790 return bless {%$params}, $class 791 if $type eq 'HASH'; 792 793 return bless [@$params], $class 794 if $type eq 'ARRAY'; 795 796 croak "Not sure how to construct a '$class' from '$params'"; 797 }; 798 799=item $mock->before($NAME, sub { ... }) 800 801This will replace the original sub C<$NAME> with a new sub that calls your 802custom code just before calling the original method. The return from your 803custom sub is ignored. Your sub and the original both get the unmodified 804arguments. 805 806=item $mock->after($NAME, sub { ... }) 807 808This is similar to before, except your callback runs after the original code. 809The return from your callback is ignored. 810 811=item $mock->around($NAME, sub { ... }) 812 813This gives you the chance to wrap the original sub: 814 815 $mock->around(foo => sub { 816 my $orig = shift; 817 my $self = shift; 818 my (@args) = @_; 819 820 ... 821 $self->$orig(@args); 822 ... 823 824 return ...; 825 }); 826 827The original sub is passed in as the first argument, even before C<$self>. You 828are responsible for making sure your wrapper sub returns the correct thing. 829 830=item $mock->autoload 831 832This will inject an C<AUTOLOAD> sub into the class. This autoload will 833automatically generate read-write accessors for any sub called that does not 834already exist. 835 836=item $mock->block_load 837 838This will prevent the real class from loading until the mock is destroyed. This 839will fail if the class is already loaded. This will let you mock a class 840completely without loading the original module. 841 842=item $pm_file = $mock->file 843 844This returns the relative path to the file for the module. This corresponds to 845the C<%INC> entry. 846 847=item $bool = $mock->purge_on_destroy($bool) 848 849When true, this will cause the package stash to be completely obliterated when 850the mock object falls out of scope or is otherwise destroyed. You do not 851normally want this. 852 853=item $stash = $mock->stash 854 855This returns the stash for the class being mocked. This is the equivalent of: 856 857 my $stash = \%{"${class}\::"}; 858 859This saves you from needing to turn off strict. 860 861=item $class = $mock->class 862 863The class being mocked by this instance. 864 865=item $p = $mock->parent 866 867If you mock a class twice the first instance is the parent, the second is the 868child. This prevents the parent from being destroyed before the child, which 869would lead to a very unpleasant situation. 870 871=item $c = $mock->child 872 873Returns the child mock, if any. 874 875=back 876 877=head1 SOURCE 878 879The source code repository for Test2-Suite can be found at 880L<https://github.com/Test-More/Test2-Suite/>. 881 882=head1 MAINTAINERS 883 884=over 4 885 886=item Chad Granum E<lt>exodist@cpan.orgE<gt> 887 888=back 889 890=head1 AUTHORS 891 892=over 4 893 894=item Chad Granum E<lt>exodist@cpan.orgE<gt> 895 896=back 897 898=head1 COPYRIGHT 899 900Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>. 901 902This program is free software; you can redistribute it and/or 903modify it under the same terms as Perl itself. 904 905See L<https://dev.perl.org/licenses/> 906 907=cut 908