File Coverage

blib/lib/Test/Mockify.pm
Criterion Covered Total %
statement 164 171 95.9
branch 30 36 83.3
condition 6 9 66.6
subroutine 34 34 100.0
pod 8 9 88.8
total 242 259 93.4


line stmt bran cond sub pod time code
1             =pod
2            
3             =head1 NAME
4            
5             Test::Mockify - minimal mocking framework for perl
6            
7             =head1 SYNOPSIS
8            
9             use Test::Mockify;
10             use Test::Mockify::Verify qw ( WasCalled );
11             use Test::Mockify::Matcher qw ( String );
12            
13             # build a new mocked object
14             my $MockObjectBuilder = Test::Mockify->new('SampleLogger', []);
15             $MockObjectBuilder->mock('log')->when(String())->thenReturnUndef();
16             my $MockedLogger = $MockLoggerBuilder->getMockObject();
17            
18             # inject mocked object into the code you want to test
19             my $App = SampleApp->new('logger'=> $MockedLogger);
20             $App->do_something();
21            
22             # verify that the mocked method was called
23             ok(WasCalled($MockedLogger, 'log'), 'log was called');
24             done_testing();
25            
26             =head1 DESCRIPTION
27            
28             Use L<Test::Mockify> to create and configure mock objects. Use L<Test::Mockify::Verify> to
29             verify the interactions with your mocks.
30            
31             =head1 METHODS
32            
33             =cut
34              
35             package Test::Mockify;
36 4     4   61000 use Test::Mockify::Tools qw ( Error ExistsMethod IsValid LoadPackage Isa );
  4         12  
  4         305  
37 4     4   1448 use Test::Mockify::TypeTests qw ( IsInteger IsFloat IsString IsArrayReference IsHashReference IsObjectReference );
  4         4  
  4         221  
38 4     4   1282 use Test::Mockify::MethodCallCounter;
  4         9  
  4         105  
39 4     4   1686 use Test::Mockify::Method;
  4         10  
  4         159  
40 4     4   1360 use Test::Mockify::MethodSpy;
  4         6  
  4         120  
41 4     4   2140 use Test::MockObject::Extends;
  4         22990  
  4         17  
42 4     4   1545 use Test::Mockify::CompatibilityTools qw (MigrateOldMatchers);
  4         8  
  4         205  
43 4     4   19 use Data::Dumper;
  4         5  
  4         155  
44 4     4   13 use Scalar::Util qw( blessed );
  4         4  
  4         176  
45 4     4   15 use Data::Compare;
  4         4  
  4         26  
46              
47 4     4   7071 use experimental 'switch';
  4         10772  
  4         22  
48              
49 4     4   481 use strict;
  4         6  
  4         5943  
50              
51             our $VERSION = '0.10.1';
52              
53             sub new {
54 59     59 0 9253     my $class = shift;
55 59         70     my ( $FakeModulePath, $aFakeParams ) = @_;
56              
57 59         91     my $self = bless {}, $class;
58              
59 59         152     LoadPackage( $FakeModulePath );
60 59         53     my $FakeClass = $FakeModulePath->new( @{$aFakeParams} );
  59         175  
61 59         403     $self->_mockedModulPath($FakeModulePath);
62 59         213     $self->_mockedSelf(Test::MockObject::Extends->new( $FakeClass ));
63 59         98     $self->_initMockedModule();
64              
65 59         106     return $self;
66              
67             }
68             #----------------------------------------------------------------------------------------
69             sub _mockedModulPath {
70 143     143   113     my $self = shift;
71 143         116     my ($ModulPath) = @_;
72 143 100       409     return $self->{'MockedModulePath'} unless ($ModulPath);
73 59         111     $self->{'MockedModulePath'} = $ModulPath;
74             }
75             #----------------------------------------------------------------------------------------
76             sub _mockedSelf {
77 510     510   4655     my $self = shift;
78 510         390     my ($MockedSelf) = @_;
79 510 100       1999     return $self->{'MockedModule'} unless ($MockedSelf);
80 59         84     $self->{'MockedModule'} = $MockedSelf;
81             }
82             #----------------------------------------------------------------------------------------
83             sub _initMockedModule {
84 59     59   52     my $self = shift;
85              
86 59         170     $self->_mockedSelf()->{'__MethodCallCounter'} = Test::Mockify::MethodCallCounter->new();
87 59         81     $self->_mockedSelf()->{'__isMockified'} = 1;
88 59         99     $self->_addGetParameterFromMockifyCall();
89              
90 59         67     return;
91             }
92              
93             #----------------------------------------------------------------------------------------
94             =pod
95            
96             =head2 getMockObject
97            
98             Provides the actual mock object, which you can use in the test.
99            
100             my $aParameterList = ['SomeValueForConstructor'];
101             my $MockObjectBuilder = Test::Mockify->new( 'My::Module', $aParameterList );
102             my $MyModuleObject = $MockObjectBuilder->getMockObject();
103            
104             =cut
105             sub getMockObject {
106 56     56 1 123     my $self = shift;
107 56         83     return $self->_mockedSelf();
108             }
109              
110             #----------------------------------------------------------------------------------------=
111             =pod
112            
113             =head2 mock
114            
115             This is place where the mocked methods are defined. The method also proves that the method you like to mock actually exists.
116            
117             =head3 synopsis
118            
119             This method takes one parameter, which is the name of the method you like to mock.
120             Because you need to specify more detailed the behaviour of this mock you have to chain the method signature (when) and the expected return value (then...).
121            
122             For example, the next line will create a mocked version of the method log, but only if this method is called with any string and the number 123. In this case it will return the String 'Hello World'. Mockify will throw an error if this method is called somehow else.
123            
124             my $MockObjectBuilder = Test::Mockify->new( 'Sample::Logger', [] );
125             $MockObjectBuilder->mock('log')->when(String(), Number(123))->thenReturn('Hello World');
126             my $SampleLogger = $MockObjectBuilder->getMockObject();
127             is($SampleLogger->log('abc',123), 'Hello World');
128            
129            
130             =head4 when
131            
132             To define the signature in the needed structure you must use the L<< Test::Mockify::Matcher >>.
133            
134             =head4 whenAny
135            
136             If you don't want to specify the method signature at all, you can use whenAny.
137             It is not possible to mix C<whenAny> and C<when> for the same method.
138            
139             =head4 then ...
140            
141             For possible return types please look in L<Test::Mockify::ReturnValue>
142            
143             =cut
144             sub mock {
145 20     20 1 71     my $self = shift;
146 20         30     my @Parameters = @_;
147                 
148 20         23     my $ParameterAmount = scalar @Parameters;
149 20 100 66     72     if($ParameterAmount == 1 && IsString($Parameters[0]) ){
150 17         32         return $self->_addMockWithMethod($Parameters[0]);
151                 }
152 3 100       6     if($ParameterAmount == 2){
153 2         4         my ( $MethodName, $ReturnValueOrFunctionPointer ) = @Parameters;
154 2 100       6         if( ref($ReturnValueOrFunctionPointer) eq 'CODE' ){
155 1         3             $self->addMock($MethodName, $ReturnValueOrFunctionPointer);
156                     }else{
157 1         4             $self->addMockWithReturnValue($MethodName, $ReturnValueOrFunctionPointer);
158                     }
159                 }
160 3 100       6     if($ParameterAmount == 3){
161 1         3         my ( $MethodName, $ReturnValue, $aParameterTypes ) = @_;
162 1         4         $self->addMockWithReturnValueAndParameterCheck($MethodName, $ReturnValue, $aParameterTypes);
163                 }
164 3         6     return;
165             }
166             =pod
167            
168             =head2 spy
169            
170             Use spy if you want to observe a method. You can use the L<Test::Mockify::Verify> to ensure that the method was called with the expected parameters.
171            
172             =head3 synopsis
173            
174             This method takes one parameter, which is the name of the method you like to spy.
175             Because you need to specify more detailed the behaviour of this spy you have to define the method signature with C<when>
176            
177             For example, the next line will create a method spy of the method log, but only if this method is called with any string and the number 123. Mockify will throw an error if this method is called in another way.
178            
179             my $MockObjectBuilder = Test::Mockify->new( 'Sample::Logger', [] );
180             $MockObjectBuilder->spy('log')->when(String(), Number(123));
181             my $SampleLogger = $MockObjectBuilder->getMockObject();
182            
183             # call spied method
184             $SampleLogger->log('abc', 123);
185            
186             # verify that the spied method was called
187             is_deeply(GetParametersFromMockifyCall($MockedLogger, 'log'),['abc', 123], 'Check parameters of first call');
188            
189             =head4 when
190            
191             To define the signature in the needed structure you must use the L<< Test::Mockify::Matcher >>.
192            
193             =head4 whenAny
194            
195             If you don't want to specify the method signature at all, you can use whenAny.
196             It is not possible to mix C<whenAny> and C<when> for the same method.
197            
198             =cut
199             sub spy {
200 16     16 1 43     my $self = shift;
201 16         21     my ($MethodName) = @_;
202 16         13     my $PointerOriginalMethod = \&{$self->_mockedModulPath().'::'.$MethodName};
  16         24  
203             #In order to have the current object available in the parameter list, it has to be injected here.
204                 return $self->_addMockWithMethodSpy($MethodName, sub {
205 15     15   23         return $PointerOriginalMethod->($self->_mockedSelf(), @_);
206 16         60     });
207             }
208              
209             #----------------------------------------------------------------------------------------
210             =pod
211            
212             =head2 addMethodSpy I<(deprecated)>
213            
214             With this method it is possible to observe a method. That means, you keep the original functionality but you can get meta data from the mockify-framework.
215            
216             $MockObjectBuilder->addMethodSpy('myMethodName');
217            
218             =cut
219             sub addMethodSpy {
220 2     2 1 9     my $self = shift;
221 2         3     my ( $MethodName ) = @_;
222 2 50       28     if (warnings::enabled("deprecated")) {
223 0         0         warnings::warn('deprecated', "addMethodSpy is deprecated, use spy('name')->whenAny()");
224                 }
225 2         143     $self->spy($MethodName)->whenAny();
226 2         4     return;
227             }
228             #----------------------------------------------------------------------------------------
229             =pod
230            
231             =head2 addMethodSpyWithParameterCheck I<(deprecated)>
232            
233             With this method it is possible to observe a method and check the parameters. That means, you keep the original functionality, but you can get meta data from the mockify- framework and use the parameter check, like B<addMockWithReturnValueAndParameterCheck>.
234            
235             my $aParameterTypes = [String(),String(abcd)];
236             $MockObjectBuilder->addMethodSpyWithParameterCheck('myMethodName', $aParameterTypes);
237            
238             To define it in a nice way the signature you must use the L<< Test::Mockify::Matcher; >>.
239            
240             =cut
241             sub addMethodSpyWithParameterCheck {
242 2     2 1 14     my $self = shift;
243 2         3     my ( $MethodName, $aParameterTypes ) = @_;
244 2 50       36     if (warnings::enabled("deprecated")) {
245 0         0         warnings::warn('deprecated', "addMethodSpyWithParameterCheck is deprecated, use spy('name')->when(String('abc'))");
246                 }
247 2         197     my $aMigratedMatchers = MigrateOldMatchers($aParameterTypes);
248 2         5     $self->spy($MethodName)->when(@{$aMigratedMatchers});
  2         9  
249 2         6     return;
250             }
251              
252             #----------------------------------------------------------------------------------------
253             =pod
254            
255             =head2 addMock I<(deprecated)>
256            
257             This is the simplest case. It works like the mock-method from L<Test::MockObject>.
258            
259             Only handover the B<name> and a B<method pointer>. Mockify will automatically check if the method exists in the original object.
260            
261             $MockObjectBuilder->addMock('myMethodName', sub {
262             # Your implementation
263             }
264             );
265            
266             =cut
267             sub addMock {
268 7     7 1 35     my $self = shift;
269 7         7     my ( $MethodName, $rSub ) = @_;
270 7 50       96     if (warnings::enabled("deprecated")) {
271 0         0         warnings::warn('deprecated', "addMock is deprecated, use mock('name')->whenAny()->thenCall(sub{})");
272                 }
273                 $self->_addMockWithMethod($MethodName)->whenAny()->thenCall(sub {
274 7     7   9         return $rSub->($self->_mockedSelf(), @_);
275 7         535     });
276              
277 7         10     return;
278             }
279             #-------------------------------------------------------------------------------------
280             sub _addMockWithMethod {
281 53     53   74     my $self = shift;
282 53         54     my ( $MethodName ) = @_;
283 53         82     $self->_testMockTypeUsage($MethodName);
284 52         138     return $self->_addMock($MethodName, Test::Mockify::Method->new());
285             }
286             #-------------------------------------------------------------------------------------
287             sub _addMockWithMethodSpy {
288 16     16   11     my $self = shift;
289 16         19     my ( $MethodName, $PointerOriginalMethod ) = @_;
290 16         23     $self->_testMockTypeUsage($MethodName);
291 15         45     return $self->_addMock($MethodName, Test::Mockify::MethodSpy->new($PointerOriginalMethod));
292             }
293             #-------------------------------------------------------------------------------------
294             sub _addMock {
295 67     67   62     my $self = shift;
296 67         75     my ( $MethodName, $Method) = @_;
297              
298 67         91     ExistsMethod( $self->_mockedModulPath(), $MethodName );
299 66         96     $self->_mockedSelf()->{'__MethodCallCounter'}->addMethod( $MethodName );
300 66 100       132     if(not $self->{'MethodStore'}{$MethodName}){
301 56   33     211         $self->{'MethodStore'}{$MethodName} //= $Method;
302                     $self->_mockedSelf()->mock($MethodName, sub {
303 74     74   8580             $self->_mockedSelf()->{'__MethodCallCounter'}->increment( $MethodName );
304 74         69             my $MockedSelf = shift;
305 74         90             my @MockedParameters = @_;
306 74         117             $self->_storeParameters( $MethodName, $MockedSelf, \@MockedParameters );
307 74         214             return $self->{'MethodStore'}{$MethodName}->call(@MockedParameters);
308 56         78         });
309                 }
310 66         1414     return $self->{'MethodStore'}{$MethodName};
311             }
312             #----------------------------------------------------------------------------------------
313             =pod
314            
315             =head2 addMockWithReturnValue I<(deprecated)>
316            
317             Does the same as C<addMock>, but here you can handover a B<value> which will be returned if you call the mocked method.
318            
319             $MockObjectBuilder->addMockWithReturnValue('myMethodName','the return value');
320            
321             =cut
322             sub addMockWithReturnValue {
323 5     5 1 51     my $self = shift;
324 5         8     my ( $MethodName, $ReturnValue ) = @_;
325 5 50       62     if (warnings::enabled("deprecated")) {
326 0         0         warnings::warn('deprecated', "addMockWithReturnValue is deprecated, use mock('name')->when()->thenReturn('Value')");
327                 }
328 5 50       442     if($ReturnValue){
329 5         11         $self->_addMockWithMethod($MethodName)->when()->thenReturn($ReturnValue);
330                 }else {
331 0         0         $self->_addMockWithMethod($MethodName)->when()->thenReturnUndef();
332                 }
333              
334 4         8     return;
335             }
336             #----------------------------------------------------------------------------------------
337             =pod
338            
339             =head2 addMockWithReturnValueAndParameterCheck I<(deprecated)>
340            
341             This method is an extension of B<addMockWithReturnValue>. Here you can also check the parameters which will be passed.
342            
343             You can check if they have a specific B<data type> or even check if they have a given B<value>.
344            
345             In the following example two strings will be expected, and the second one has to have the value "abcd".
346            
347             my $aParameterTypes = [String(),String('abcd')];
348             $MockObjectBuilder->addMockWithReturnValueAndParameterCheck('myMethodName','the return value',$aParameterTypes);
349            
350             To define it in a nice way the signature you must use the L<< Test::Mockify::Matcher; >>.
351            
352             =cut
353             sub addMockWithReturnValueAndParameterCheck {
354 26     26 1 207     my $self = shift;
355 26         32     my ( $MethodName, $ReturnValue, $aParameterTypes ) = @_;
356 26 50       365     if (warnings::enabled("deprecated")) {
357 0         0         warnings::warn('deprecated', "addMockWithReturnValue is deprecated, use mock('name')->when(String('abc'))->thenReturn('Value')");
358                 }
359 26 100       1960     if ( not IsArrayReference( $aParameterTypes ) ){
360 1         3         Error( 'ParameterTypesNotProvided', {
361                         'Method' => $self->_mockedModulPath()."->$MethodName",
362                         'ParameterList' => $aParameterTypes,
363                     } );
364                 }
365 25         53     $aParameterTypes = MigrateOldMatchers($aParameterTypes);
366              
367 24 100       33     if($ReturnValue){
368 18         39         $self->_addMockWithMethod($MethodName)->when(@{$aParameterTypes})->thenReturn($ReturnValue);
  18         45  
369                 }else {
370 6         14         $self->_addMockWithMethod($MethodName)->when(@{$aParameterTypes})->thenReturnUndef();
  6         14  
371                 }
372              
373 24         61     return;
374             }
375             #----------------------------------------------------------------------------------------
376             sub _storeParameters {
377 74     74   62     my $self = shift;
378 74         70     my ( $MethodName, $MockedSelf, $aMockedParameters ) = @_;
379              
380 74         40     push( @{$MockedSelf->{$MethodName.'_MockifyParams'}}, $aMockedParameters );
  74         168  
381              
382 74         74     return;
383             }
384              
385             #----------------------------------------------------------------------------------------
386             sub _addGetParameterFromMockifyCall {
387 59     59   43     my $self = shift;
388              
389                 $self->_mockedSelf()->mock('__getParametersFromMockifyCall',
390                     sub{
391 12     12   522             my $MockedSelf = shift;
392 12         16             my ( $MethodName, $Position ) = @_;
393              
394 12         32             my $aParametersFromAllCalls = $MockedSelf->{$MethodName.'_MockifyParams'};
395 12 100       32             if( ref $aParametersFromAllCalls ne 'ARRAY' ){
396 1         6                 Error( "$MethodName was not called" );
397                         }
398 11 100       12             if( scalar @{$aParametersFromAllCalls} < $Position ) {
  11         24  
399 1         7                 Error( "$MethodName was not called ".( $Position+1 ).' times',{
400                             'Method' => "$MethodName",
401                             'Postion' => $Position,
402                             } );
403                         }
404                         else {
405 10         24                 my $ParameterFromMockifyCall = $MockedSelf->{$MethodName.'_MockifyParams'}[$Position];
406 10         48                 return $ParameterFromMockifyCall;
407                         }
408 0         0             return;
409                     }
410 59         78     );
411              
412 59         1432     return;
413             }
414             #----------------------------------------------------------------------------------------
415             sub _testMockTypeUsage {
416 69     69   57     my $self = shift;
417 69         343     my ($MethodName) = @_;
418 69         55     my $PositionInCallerStack = 2;
419 69         171     my $MethodMockType = (caller($PositionInCallerStack))[3]; # autodetect mock type (spy or mock)
420 69 100 100     578     if($self->{'MethodMockType'}{$MethodName} && $self->{'MethodMockType'}{$MethodName} ne $MethodMockType){
421 2         18         die('It is not possible to mix spy and mock');
422                 }else{
423 67         116         $self->{'MethodMockType'}{$MethodName} = $MethodMockType;
424                 }
425 67         66     return;
426             }
427             1;
428              
429             __END__
430            
431             =head1 LICENSE
432            
433             Copyright (C) 2017 ePages GmbH
434            
435             This library is free software; you can redistribute it and/or modify
436             it under the same terms as Perl itself.
437            
438             =head1 AUTHOR
439            
440             Christian Breitkreutz E<lt>christianbreitkreutz@gmx.deE<gt>
441            
442             =cut
443            
444