File Coverage

blib/lib/Test/ExpectAndCheck/Future.pm
Criterion Covered Total %
statement 58 64 90.6
branch 12 14 85.7
condition 4 6 66.6
subroutine 16 18 88.8
pod n/a
total 90 102 88.2


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   53188 use strict;
  2         6  
  2         44  
9 2     2   7 use warnings;
  2         3  
  2         38  
10 2     2   7 use base qw( Test::ExpectAndCheck );
  2         3  
  2         437  
11              
12             our $VERSION = '0.05';
13              
14 2     2   10 use constant EXPECTATION_CLASS => "Test::ExpectAndCheck::Future::_Expectation";
  2         3  
  2         111  
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   9 use base qw( Test::ExpectAndCheck::_Expectation );
  2         4  
  2         512  
67              
68 2     2   686 use Test::Future::Deferred;
  2         19344  
  2         46  
69              
70 2     2   9 use Carp;
  2         10  
  2         112  
71             our @CARP_NOT = qw( Test::ExpectAndCheck );
72              
73             use constant {
74 2         968 BEHAVE_NOFUTURE => 0,
75             BEHAVE_DONE => 1,
76             BEHAVE_FAIL => 2,
77             BEHAVE_PENDING => 3,
78             BEHAVE_IMM_MASK => 4,
79 2     2   10 };
  2         2  
80              
81             =head1 EXPECTATIONS
82              
83             =cut
84              
85             =head2 will_done
86              
87             $exp->will_done( @result );
88              
89             I
90              
91             Sets that method call will return a C instance which will succeed
92             with the given result.
93              
94             =cut
95              
96             sub will_done
97             {
98 5     5   6 my $self = shift;
99              
100 5   100     17 my $imm = ( $self->{behaviour}[0] // 0 ) & BEHAVE_IMM_MASK;
101 5         11 $self->{behaviour} = [ BEHAVE_DONE|$imm, @_ ];
102              
103 5         15 return $self;
104             }
105              
106             # This was a bad API; "returns" on a T:EAC:Future expectation would set the
107             # future done result, not the immediate method call result
108             sub returns
109             {
110 0     0   0 warnings::warnif deprecated => "Calling \$exp->returns() on a Future expectation is now deprecated; use ->will_done instead";
111 0         0 return shift->will_done( @_ );
112             }
113              
114             =head2 will_fail
115              
116             $exp->will_fail( $message, $category, @more );
117              
118             I
119              
120             Sets that method call will return a C instance which will fail
121             with the given message, and optionally category name and extra details.
122              
123             =cut
124              
125             sub will_fail
126             {
127 1     1   1 my $self = shift;
128              
129 1   50     6 my $imm = ( $self->{behaviour}[0] // 0 ) & BEHAVE_IMM_MASK;
130 1         3 $self->{behaviour} = [ BEHAVE_FAIL|$imm, @_ ];
131              
132 1         1 return $self;
133             }
134              
135             sub fails
136             {
137 0     0   0 warnings::warnif deprecated => "Calling \$exp->fails() is now deprecated; use ->will_fail instead";
138 0         0 return shift->will_fail( @_ );
139             }
140              
141             # Reset the future-type behaviour on these
142             sub will_return_using
143             {
144 8     8   9 my $self = shift;
145              
146 8         18 $self->SUPER::will_return_using( @_ );
147 8         10 $self->{behaviour} = [ BEHAVE_NOFUTURE ];
148              
149 8         12 return $self;
150             }
151              
152             =head2 immediately
153              
154             $exp->will_done( ... )->immediately;
155              
156             $exp->will_fail( ... )->immediately;
157              
158             I
159              
160             Switches this expectation to return an immediate future, rather than a
161             deferred one.
162              
163             =cut
164              
165             sub immediately
166             {
167 1     1   2 my $self = shift;
168              
169 1         12 $self->{behaviour}[0] |= BEHAVE_IMM_MASK;
170              
171 1         3 return $self;
172             }
173              
174             =head2 remains_pending
175              
176             $exp->remains_pending;
177              
178             I
179              
180             Sets that the future returned by this method will not complete and simply
181             remain pending.
182              
183             =cut
184              
185             sub remains_pending
186             {
187 1     1   2 my $self = shift;
188              
189 1         2 $self->{behaviour}[0] = BEHAVE_PENDING;
190              
191 1         2 return $self;
192             }
193              
194             =head2 will_also_later
195              
196             $exp->will_also_later( sub { ... } );
197              
198             I
199              
200             Adds extra code which will run when the expected method is called, after the
201             returned future has completed. This is performed by the use of
202             C.
203              
204             When invoked, the code body is invoked in void context with no additional
205             arguments.
206              
207             =cut
208              
209             sub will_also_later
210             {
211 1     1   2 my $self = shift;
212              
213 1         1 push @{ $self->{also_later} }, @_;
  1         2  
214              
215 1         2 return $self;
216             }
217              
218             sub _result
219             {
220 8     8   10 my $self = shift;
221              
222 8   50     16 my $behaviour = $self->{behaviour} // [ 0 ];
223 8         14 my ( $type, @args ) = @$behaviour;
224              
225             # $type == BEHAVE_NOFUTURE is zero and ignored
226 8 100       22 if( $type == BEHAVE_DONE ) {
    100          
    100          
    50          
    100          
    50          
227 4         11 $self->SUPER::will_return( Test::Future::Deferred->done_later( @args ) );
228             }
229             elsif( $type == (BEHAVE_DONE|BEHAVE_IMM_MASK) ) {
230 1         4 $self->SUPER::will_return( Future->done( @args ) );
231             }
232             elsif( $type == BEHAVE_FAIL ) {
233 1         3 $self->SUPER::will_return( Test::Future::Deferred->fail_later( @args ) );
234             }
235             elsif( $type == (BEHAVE_FAIL|BEHAVE_IMM_MASK) ) {
236 0         0 $self->SUPER::will_return( Future->fail( @args ) );
237             }
238             elsif( $type == BEHAVE_PENDING ) {
239 1         3 $self->SUPER::will_return( Future->new );
240             }
241             elsif( $type ) {
242 0         0 die "TODO: Need result type $type";
243             }
244              
245 8 100       17 if( my $also_later = $self->{also_later} ) {
246             Test::Future::Deferred->done_later
247 1     1   86 ->on_done( sub { $_->() for @$also_later } )
248 1         4 ->retain;
249             }
250              
251 8         68 return $self->SUPER::_result( @_ );
252             }
253              
254             =head1 AUTHOR
255              
256             Paul Evans
257              
258             =cut
259              
260             0x55AA;