File Coverage

blib/lib/Test/ExpectAndCheck/Future.pm
Criterion Covered Total %
statement 61 71 85.9
branch 12 14 85.7
condition 4 6 66.6
subroutine 17 20 85.0
pod n/a
total 94 111 84.6


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, 2020 -- leonerd@leonerd.org.uk
5              
6             package Test::ExpectAndCheck::Future;
7              
8 2     2   63416 use strict;
  2         8  
  2         49  
9 2     2   10 use warnings;
  2         3  
  2         47  
10 2     2   9 use base qw( Test::ExpectAndCheck );
  2         4  
  2         531  
11              
12             our $VERSION = '0.04';
13              
14 2     2   12 use constant EXPECTATION_CLASS => "Test::ExpectAndCheck::Future::_Expectation";
  2         4  
  2         122  
15              
16             =head1 NAME
17              
18             C - C-style unit testing with C-returning methods
19              
20             =head1 SYNOPSIS
21              
22             use Test::More;
23             use Test::ExpectAndCheck::Future;
24              
25             use Future::AsyncAwait;
26              
27             my ( $controller, $mock ) = Test::ExpectAndCheck::Future->create;
28              
29             {
30             $controller->expect( act => 123, 45 )
31             ->will_done( 678 );
32              
33             is( await $mock->act( 123, 45 ), 678, '$mock->act yields result' );
34              
35             $controller->check_and_clear( '->act' );
36             }
37              
38             done_testing;
39              
40             =head1 DESCRIPTION
41              
42             This package creates objects that assist in writing unit tests with mocked
43             object instances. Each mocked instance will expect to receive a given list of
44             method calls. Each method call is checked that it received the right
45             arguments, and will return a L instance to yield the prescribed
46             result. At the end of each test, each object is checked to ensure all the
47             expected methods were called.
48              
49             It is a variation of L, assistance around the results
50             of invoked methods. Every invoked method will return a L instance. The
51             L or L method can then set the desired eventual result
52             of that future instance for each expectation.
53              
54             These return instances are implemented using L, so
55             they are not immediately ready. Instead they will only become ready after a
56             toplevel C expression or call to the C method. This should help
57             unit tests to run similarly to real-world behaviour, where most futures
58             returned by real-world interfaces (such as IO systems) would not be
59             immediately ready. This behaviour can be switched off for individual
60             expectations by using the L method.
61              
62             =cut
63              
64             package
65             Test::ExpectAndCheck::Future::_Expectation;
66 2     2   11 use base qw( Test::ExpectAndCheck::_Expectation );
  2         4  
  2         594  
67              
68 2     2   814 use Test::Future::Deferred;
  2         22581  
  2         53  
69              
70 2     2   12 use Carp;
  2         3  
  2         130  
71             our @CARP_NOT = qw( Test::ExpectAndCheck );
72              
73             use constant {
74 2         122 BEHAVIOUR => 9,
75             ALSO_LATER => 10,
76 2     2   11 };
  2         3  
77              
78             use constant {
79 2         1222 BEHAVE_NOFUTURE => 0,
80             BEHAVE_DONE => 1,
81             BEHAVE_FAIL => 2,
82             BEHAVE_PENDING => 3,
83             BEHAVE_IMM_MASK => 4,
84 2     2   9 };
  2         3  
85              
86             =head1 EXPECTATIONS
87              
88             =head2 will_done
89              
90             =head2 will_fail
91              
92             =cut
93              
94             sub will_done
95             {
96 5     5   8 my $self = shift;
97              
98 5   100     22 my $imm = ( $self->[BEHAVIOUR][0] // 0 ) & BEHAVE_IMM_MASK;
99 5         11 $self->[BEHAVIOUR] = [ BEHAVE_DONE|$imm, @_ ];
100              
101 5         19 return $self;
102             }
103              
104             # This was a bad API; "returns" on a T:EAC:Future expectation would set the
105             # future done result, not the immediate method call result
106             sub returns
107             {
108 0     0   0 warnings::warnif deprecated => "Calling \$exp->returns() on a Future expectation is now deprecated; use ->will_done instead";
109 0         0 return shift->will_done( @_ );
110             }
111              
112             sub will_fail
113             {
114 1     1   2 my $self = shift;
115              
116 1   50     7 my $imm = ( $self->[BEHAVIOUR][0] // 0 ) & BEHAVE_IMM_MASK;
117 1         3 $self->[BEHAVIOUR] = [ BEHAVE_FAIL|$imm, @_ ];
118              
119 1         2 return $self;
120             }
121              
122             sub fails
123             {
124 0     0   0 warnings::warnif deprecated => "Calling \$exp->fails() is now deprecated; use ->will_fail instead";
125 0         0 return shift->will_fail( @_ );
126             }
127              
128             # Reset the future-type behaviour on these
129             sub will_return
130             {
131 1     1   13 my $self = shift;
132              
133 1         4 $self->SUPER::will_return( @_ );
134 1         4 $self->[BEHAVIOUR] = [ BEHAVE_NOFUTURE ];
135              
136 1         2 return $self;
137             }
138              
139             sub will_throw
140             {
141 0     0   0 my $self = shift;
142              
143 0         0 $self->SUPER::will_throw( @_ );
144 0         0 $self->[BEHAVIOUR] = [ BEHAVE_NOFUTURE ];
145              
146 0         0 return $self;
147             }
148              
149             =head2 immediately
150              
151             $exp->will_done( ... )->immediately
152              
153             $exp->will_fail( ... )->immediately
154              
155             I
156              
157             Switches this expectation to return an immediate future, rather than a
158             deferred one.
159              
160             =cut
161              
162             sub immediately
163             {
164 1     1   2 my $self = shift;
165              
166 1         3 $self->[BEHAVIOUR][0] |= BEHAVE_IMM_MASK;
167              
168 1         3 return $self;
169             }
170              
171             =head2 remains_pending
172              
173             $exp->remains_pending
174              
175             I
176              
177             Sets that the future returned by this method will not complete and simply
178             remain pending.
179              
180             =cut
181              
182             sub remains_pending
183             {
184 1     1   2 my $self = shift;
185              
186 1         3 $self->[BEHAVIOUR][0] = BEHAVE_PENDING;
187              
188 1         2 return $self;
189             }
190              
191             =head2 will_also_later
192              
193             $exp->will_also_later( sub { ... } );
194              
195             I
196              
197             Adds extra code which will run when the expected method is called, after the
198             returned future has completed. This is performed by the use of
199             C.
200              
201             When invoked, the code body is invoked in void context with no additional
202             arguments.
203              
204             =cut
205              
206             sub will_also_later
207             {
208 1     1   3 my $self = shift;
209              
210 1         2 push @{ $self->[ALSO_LATER] }, @_;
  1         3  
211              
212 1         2 return $self;
213             }
214              
215             sub _result
216             {
217 8     8   12 my $self = shift;
218              
219 8   50     17 my $behaviour = $self->[BEHAVIOUR] // [ 0 ];
220 8         17 my ( $type, @args ) = @$behaviour;
221              
222             # $type == BEHAVE_NOFUTURE is zero and ignored
223 8 100       52 if( $type == BEHAVE_DONE ) {
    100          
    100          
    50          
    100          
    50          
224 4         13 $self->SUPER::will_return( Test::Future::Deferred->done_later( @args ) );
225             }
226             elsif( $type == (BEHAVE_DONE|BEHAVE_IMM_MASK) ) {
227 1         9 $self->SUPER::will_return( Future->done( @args ) );
228             }
229             elsif( $type == BEHAVE_FAIL ) {
230 1         4 $self->SUPER::will_return( Test::Future::Deferred->fail_later( @args ) );
231             }
232             elsif( $type == (BEHAVE_FAIL|BEHAVE_IMM_MASK) ) {
233 0         0 $self->SUPER::will_return( Future->fail( @args ) );
234             }
235             elsif( $type == BEHAVE_PENDING ) {
236 1         5 $self->SUPER::will_return( Future->new );
237             }
238             elsif( $type ) {
239 0         0 die "TODO: Need result type $type";
240             }
241              
242 8 100       18 if( my $also_later = $self->[ALSO_LATER] ) {
243             Test::Future::Deferred->done_later
244 1     1   124 ->on_done( sub { $_->() for @$also_later } )
245 1         11 ->retain;
246             }
247              
248 8         94 return $self->SUPER::_result;
249             }
250              
251             =head1 AUTHOR
252              
253             Paul Evans
254              
255             =cut
256              
257             0x55AA;