File Coverage

blib/lib/Test/Mockify/Sut.pm
Criterion Covered Total %
statement 65 65 100.0
branch 14 14 100.0
condition 14 20 70.0
subroutine 14 14 100.0
pod 6 7 85.7
total 113 120 94.1


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             Test::Mockify::Sut - injection options for your System under test (Sut) based on Mockify
6              
7             =head1 SYNOPSIS
8              
9             use Test::Mockify::Sut;
10             use Test::Mockify::Verify qw ( WasCalled );
11             use Test::Mockify::Matcher qw ( String );
12              
13             # build a new system under text
14             my $MockifySut = Test::Mockify::Sut->new('Package::I::Like::To::Test', []);
15             $MockifySut->mockImported('Package::Name', 'ImportedFunctionName')->when(String())->thenReturn('Hello');
16             $MockifySut->mockStatic('Fully::Qualified::FunctionName')->when(String())->thenReturn('Hello');
17             $MockifySut->mockConstructor('Package::Name', $Object);# hint: build this object also with Mockify
18             my $PackageILikeToTest = $MockifySut->getMockObject();
19              
20             $PackageILikeToTest->do_something();# all injections are used here
21              
22             # verify that the mocked method were called
23             ok(WasCalled($PackageILikeToTest, 'ImportedFunctionName'), 'ImportedFunctionName was called');
24             done_testing();
25              
26             =head1 DESCRIPTION
27              
28             Use L to create and configure Sut objects. Use L to
29             verify the interactions with your mocks.
30              
31             You can find a Example Project in L
32              
33             =head1 METHODS
34              
35             =cut
36              
37             package Test::Mockify::Sut;
38 9     9   586556 use strict;
  9         113  
  9         226  
39 9     9   39 use warnings;
  9         17  
  9         210  
40 9     9   37 use parent 'Test::Mockify';
  9         13  
  9         55  
41 9     9   470 use Test::Mockify::Matcher qw (String);
  9         18  
  9         427  
42 9     9   67 use Test::Mockify::Tools qw (Error);
  9         17  
  9         334  
43 9     9   51 use Test::Mockify::TypeTests qw ( IsString );
  9         17  
  9         6420  
44              
45             sub new {
46 46     46 0 46489 my $class = shift;
47 46         206 my $self = $class->SUPER::new(@_);
48 46         103 return $self;
49             }
50              
51             =pod
52              
53             =head2 mockImported
54              
55             Sometimes it is not possible to inject the dependencies from the outside. This is especially the case when the package uses imports of static functions.
56             C provides the possibility to mock imported functions inside the mock.
57              
58             Unlike C is the injection with C only in the mock valid.
59              
60             =head3 synopsis
61              
62             package Show::Magician;
63             use Magic::Tools qw ( Rabbit );
64             sub pullCylinder {
65             shift;
66             if(Rabbit('white')){
67             return 1;
68             }else{
69             return 0;
70             }
71             }
72             1;
73              
74              
75             In the Test it can be mocked
76              
77             package Test_Magician;
78             use Magic::Tools qw ( Rabbit );
79             my $Mockify = Test::Mockify::Sut->new( 'Show::Magician', [] );
80             $Mockify->mockImported('Magic::Tools','Rabbit')->when(String('white'))->thenReturn(1);
81              
82             my $Magician = $Mockify->getMockObject();
83             is($Magician ->pullCylinder(), 1);
84             Rabbit('white');# return original result
85             1;
86              
87              
88             It can be mixed with normal C
89              
90             =cut
91             sub mockImported {
92 17     17 1 850 my $self = shift;
93 17         51 my @Parameters = @_;
94              
95 17         36 my $ParameterAmount = scalar @Parameters;
96 17 100 66     106 if($ParameterAmount == 2 && IsString($Parameters[0]) && IsString($Parameters[1])){
      66        
97 13         52 $self->{'IsImportedMockStore'}{$Parameters[1]} = {
98             'Path' => $Parameters[0],
99             'MethodName' => $Parameters[1],
100             };
101 13         59 return $self->_addMockWithMethod($Parameters[1]);
102             }else{
103 4         18 Error('"mockImported" Needs to be called with two Parameters which need to be a fully qualified path as String and the Function name. e.g. "Path::To::Your", "Function"');
104             }
105              
106             }
107             =pod
108              
109             =head2 spyImported
110              
111             C provides the possibility to spy imported functions inside the mock.
112              
113             Unlike C is the injection with C only in the mock valid.
114              
115             =head3 synopsis
116              
117             package Show::Magician;
118             use Magic::Tools qw ( Rabbit );
119             sub pullCylinder {
120             shift;
121             if(Rabbit('white')){
122             return 1;
123             }else{
124             return 0;
125             }
126             }
127             1;
128              
129              
130             In the Test it can be mocked
131              
132             package Test_Magician;
133             use Magic::Tools qw ( Rabbit );
134             my $Mockify = Test::Mockify::Sut->new( 'Show::Magician', [] );
135             $Mockify->spyImported('Magic::Tools','Rabbit')->when(String());
136              
137             my $Magician = $Mockify->getMockObject();
138             is($Magician->pullCylinder(), 'SomeValue');
139             is(GetCallCount($Magician, 'Rabbit'), 1);
140             1;
141              
142             It can be mixed with normal C
143              
144             =cut
145             sub spyImported {
146 9     9 1 807 my $self = shift;
147 9         22 my @Parameters = @_;
148              
149 9         16 my $ParameterAmount = scalar @Parameters;
150 9 100 66     40 if($ParameterAmount == 2 && IsString($Parameters[0]) && IsString($Parameters[1])){
      66        
151 5         22 $self->{'IsImportedMockStore'}{$Parameters[1]} = {
152             'Path' => $Parameters[0],
153             'MethodName' => $Parameters[1],
154             };
155 5         7 my $PointerOriginalMethod = \&{sprintf ('%s::%s', $self->_mockedModulePath(), $Parameters[1])};
  5         13  
156 5         25 return $self->_addMockWithMethodSpy($Parameters[1], $PointerOriginalMethod);
157             }else{
158 4         11 Error('"spyImported" Needs to be called with two Parameters which need to be a fully qualified path as String and the Function name. e.g. "Path::To::Your", "Function"');
159             }
160              
161             }
162             =pod
163              
164             =head2 mockStatic
165              
166             Sometimes it is not possible to inject the dependencies from the outside.
167             C provides the possibility to mock static functions inside the mock.
168              
169             Attention: The mocked function is valid as long as the $Mockify is defined. If You leave the scope or set the $Mockify to undef the injected method will be released.
170              
171             =head3 synopsis
172              
173             package Show::Magician;
174             use Magic::Tools;
175             sub pullCylinder {
176             shift;
177             if(Magic::Tools::Rabbit('black')){
178             return 1;
179             }else{
180             return 0;
181             }
182             }
183             1;
184              
185              
186             In the Test it can be mocked like:
187              
188             package Test_Magician;
189             { # start scope
190             my $Mockify = Test::Mockify::Sut->new( 'Show::Magician', [] );
191             $Mockify->mockStatic('Magic::Tools::Rabbit')->when(String('black'))->thenReturn(1);
192             $Mockify->spy('log')->when(String());
193             my $Magician = $Mockify->getMockObject();
194              
195             is($Magician->pullCylinder('black'), 1);
196             is(Magic::Tools::Rabbit('black'), 1);
197             } # end scope
198             is(Magic::Tools::Rabbit('black'), 'someValue'); # The orignal method in in place again
199              
200              
201             It can be mixed with normal C
202              
203             =head4 ACKNOWLEDGEMENTS
204             Thanks to @dbucky for this amazing idea
205              
206             =cut
207             sub mockStatic {
208 22     22 1 974 my $self = shift;
209 22         46 my @Parameters = @_;
210              
211 22         37 my $ParameterAmount = scalar @Parameters;
212 22 100 66     137 if($ParameterAmount == 1 && IsString($Parameters[0])){
213 20 100       119 if( $Parameters[0] =~ /.*::.*/xsm ){
214 18         61 $self->{'IsStaticMockStore'}{$Parameters[0]} = 1;
215 18         85 return $self->_addMockWithMethod($Parameters[0]);
216             }else{
217 2         11 Error("The function you like to mock needs to be defined with a fully qualified path. e.g. 'Path::To::Your::$Parameters[0]' instead of only '$Parameters[0]'");
218             }
219             }else{
220 2         9 Error('"mockStatic" Needs to be called with one Parameter which need to be a fully qualified path as String. e.g. "Path::To::Your::Function"');
221             }
222              
223             }
224             =pod
225              
226             =head2 spyStatic
227              
228             Provides the possibility to spy static functions around the mock.
229              
230             =head3 synopsis
231              
232             package Show::Magician;
233             sub pullCylinder {
234             shift;
235             if(Magic::Tools::Rabbit('black')){
236             return 1;
237             }else{
238             return 0;
239             }
240             }
241             1;
242              
243             In the Test it can be mocked
244              
245             package Test_Magician;
246             use Magic::Tools;
247             my $Mockify = Test::Mockify::Sut->new( 'Show::Magician', [] );
248             $Mockify->spyStatic('Magic::Tools::Rabbit')->whenAny();
249             my $Magician = $Mockify->getMockObject();
250              
251             $Magician->pullCylinder();
252             Magic::Tools::Rabbit('black');
253             is(GetCallCount($Magician, 'Magic::Tools::Rabbit'), 2); # count as long as $Mockify is valid
254              
255             1;
256              
257             It can be mixed with normal C. For more options see, C
258              
259             =cut
260             sub spyStatic {
261 9     9 1 902 my $self = shift;
262 9         18 my ($MethodName) = @_;
263 9 100       20 if(! $MethodName){
264 2         7 Error('"spyStatic" Needs to be called with one Parameter which need to be a fully qualified path as String. e.g. "Path::To::Your::Function"');
265             }
266 7 100       33 if( $MethodName =~ /.*::.*/xsm){
267 5         16 $self->{'IsStaticMockStore'}{$MethodName} = 1;
268 5         6 my $PointerOriginalMethod = \&{$MethodName};
  5         15  
269             #In order to have the current object available in the parameter list, it has to be injected here.
270             return $self->_addMockWithMethodSpy($MethodName, sub {
271 7     7   21 return $PointerOriginalMethod->(@_);
272 5         35 });
273             }else{
274 2         12 Error("The function you like to spy needs to be defined with a fully qualified path. e.g. 'Path::To::Your::$MethodName' instead of only '$MethodName'");
275             }
276             }
277             =pod
278              
279             =head2 mockConstructor
280              
281             Sometimes it is not possible to inject the dependencies from the outside. This method gives you the posibility to override the constructor of a package where your Sut depends on.
282             The defaut constructor is C if you need another constructor name, use the third parameter.
283              
284             Attention: The mocked constructor is valid as long as the Mockify object is defined. If You leave the scope or set the Mockify object to undef the injected constructor will be released.
285              
286             =head3 synopsis
287              
288             package Path::To::SUT;
289             use Path::To::Package;
290             sub callToAction {
291             shift;
292             return Path::To::Package->new()->doAction();
293             }
294             1;
295              
296             In the Test it can be mocked like:
297              
298             package Test_SUT;
299             { # start scope
300             my $MockifySut = Test::Mockify::Sut->new( 'Path::To::SUT', [] );
301             $MockifySut->mockConstructor('Path::To::Package', $self->_createPathToPackage());
302             my $Test_SUT = $MockifySut->getMockObject();
303              
304             is($Test_SUT->callToAction(), 'hello');
305             } # end scope
306              
307             sub _createPathToPackage{
308             my $self = shift;
309             my $Mockify = Test::Mockify::Sut->new( 'Path::To::Package', [] );
310             $Mockify->mock('doAction')->when()->thenReturn('hello');
311             return $Mockify->getMockObject();
312             }
313              
314             It can be mixed with normal C.
315              
316             =cut
317             sub mockConstructor {
318 5     5 1 58 my $self = shift;
319 5         10 my ($PackageName, $Object, $ConstructorName) = @_;
320 5   100     22 $ConstructorName //= 'new';
321 5 100 66     18 if($PackageName && IsString($PackageName)){
322             #note for my self: It is not possible to use thenReturn one level up since 'mockStatic' will mock the constructor before this line has finished.
323             # It is impossible to create an instance with the already mocked constructor.
324 4         41 return $self->mockStatic( sprintf('%s::%s', $PackageName, $ConstructorName) )->whenAny()->thenReturn($Object);
325             }else{
326 1         4 Error('Wrong or missing parameter list. Please use it like: $Mockify->mockConstructor(\'Path::To::Package\', $Object, \'new\')'); ## no critic (RequireInterpolationOfMetachars)
327             }
328             }
329             =pod
330              
331             =head2 getVerificationObject
332              
333             Provides the actual mock object, which you can use for verification.
334             This is code sugar for the method C.
335              
336             my $Mockify = Test::Mockify::Sut->new( 'My::Module', [] );
337             my $VerificationObject = $Mockify->getVerificationObject();
338             ok(WasCalled($VerificationObject, 'FunctionName'));
339             =cut
340             sub getVerificationObject{
341 4     4 1 450 my $self = shift;
342 4         34 return $self->getMockObject();
343             }
344              
345             1;