dbucky / mockify Goto Github PK
View Code? Open in Web Editor NEWThis project forked from epages-de/mockify
minimal mocking framework for perl
License: Other
This project forked from epages-de/mockify
minimal mocking framework for perl
License: Other
Currently this is not the case, but it is very important.
This proposed test will fail because $Mockify is not allowing itself to be cleaned up when it goes out of scope (its DESTROY method is never called).
sub test_mockRevertsWhenInjectorGoesOutOfScope {
my $self = shift;
my $SubTestName = (caller(0))[3];
my $originalValue = FakeModuleForMockifyTest::DummyMethodForTestOverriding();
my $mockValue = 'A mock dummy method';
{
my $Mockify = Test::Mockify->new('FakeModuleForMockifyTest');
$Mockify->mockStatic('FakeModuleForMockifyTest::DummyMethodForTestOverriding')->when()->thenReturn($mockValue);
is(FakeModuleForMockifyTest::DummyMethodForTestOverriding(), $mockValue, "$SubTestName - prove mock is injection");
}
is(FakeModuleForMockifyTest::DummyMethodForTestOverriding(), $originalValue, "$SubTestName - prove mock is reverted");
}
This is important to fix because it could cause memory leaks and tests being affected by prior ones in the same run.
I created test_mockSUT to illustrate the problem:
sub test_mockSUT {
my $self = shift;
my $SubTestName = (caller(0))[3];
my $injector = Test::Mockify::Injector->new('FakeModuleStaticInjection');
$injector->spy('dependency')->when();
warn FakeModuleStaticInjection->client();
ok(WasCalled($injector->getVerifier(), 'dependency'), "$SubTestName - prove SUT can be mocked directly")
}
package FakeModuleStaticInjection;
sub dependency {
my ($self, $arg) = @_;
return "$arg dependency";
}
sub client {
my $self = shift;
return $self->dependency('client');
}
The test fails when FakeModuleStaticInjection->client()
is invoked, with this message:
No matching found for stringstring -> $VAR1 = [
'FakeModuleStaticInjection',
'client'
];
This is not an issue when a dependent module is mocked, as the first argument is shifted off by the SUT before the dependent function is called with the remaining arguments. It may be argued that you shouldn't do this sort of thing, but it seems reasonable that you may want to test one function in a module that relies on another function in the same module. This is the scenario I am covering with the above test.
Ideally, the injection capabilities of Mockify should not pollute the original implementation, but rather augment it. I like how Mockify was originally constructed to not interfere at all with the modules being mocked, and I think that should remain a core aspect of it.
However, I also am aware that it is not always possible to get the mock object into the system under test without modifying it for that express purpose. That is why the injection functionality was added. The purpose of this issue is to bring to light the inherent differences between the two approaches.
Let's say you have a Dog
class with a breed
function, which simply returns the breed specified when the Dog instance was created.
package Dog;
use strict;
use warnings;
sub new {
my ($class, $breed) = @_;
my $self = bless { breed => $breed }, $class;
return $self;
}
sub breed {
my $self = shift;
return $self->{'breed'};
}
1;
Now let's mock this Dog using both approaches. The original usage of Mockify gives us something like the following. It creates an instance of the Dog class and then overrides the breed method on that instance only! This approach is useful when you have the ability to pass in an object instance to the system under test. In this case, the test object needs to be created ahead of time, and so the fact that a call to Test::Mockify->new
creates the object instance on your behalf and passes along the necessary parameters to the constructor makes complete sense.
sub original_dog {
my $dog = Dog->new('Dalmatian');
is($dog->breed(), 'Dalmatian', "Original is a Dalmatian");
my $mockify = Test::Mockify->new('Dog', ['Dalmatian']);
$mockify->mock('breed')->whenAny()->thenReturn('Great Dane');
my $mockObject = $mockify->getMockObject();
is($mockObject->breed(), 'Great Dane', 'Mock is a Great Dane');
is($dog->breed(), 'Dalmatian', "Original is a Dalmatian");
ok(WasCalled($mockObject, 'breed'), 'Mocked breed method was called');
}
The static approach is somewhat simplified by comparison, since the mocked functionality is replacing the original implementation at the class level, and therefore applies to all object instances or static functions. The approach is useful for a system under test that creates a new instance of a class or calls a static function that you want to mock. In this case, the system under test handles setting up the instance, so Mockify should not be creating its own. The mocked class can be called directly in the test and provide the mocked functionality. The injector instance could provide the verification capabilities, as there is no mocked object.
sub static_dog {
my $dog = Dog->new('Dalmatian');
is($dog->breed(), 'Dalmatian', "Original is a Dalmatian");
my $injector = Test::Mockify->new('Dog'); # should not call Dog->new() or need constructor parameters
$injector->mockStatic('breed')->whenAny()->thenReturn('Great Dane');
is(Dog->breed(), 'Great Dane', 'Mock is a Great Dane');
is($dog->breed(), 'Great Dane', "Original is now a Great Dane");
ok(WasCalled($injector, 'breed'), 'Mocked breed method was called');
}
To summarize:
Because of these differences between the two approaches, having the injector be a separate module that inherits from Test::Mockify
would allow it to diverge enough to achieve these goals.
This is the first static test:
sub test_mockStatic {
my $self = shift;
my $SubTestName = (caller(0))[3];
my $SUT;
{
my $Mockify = Test::Mockify->new('FakeModulStaticInjection',[]);
$Mockify->mockStatic('FakeStaticTools::ReturnHelloWorld')->when(String('German'))->thenReturn('Hallo Welt');
$Mockify->mockStatic('FakeStaticTools::ReturnHelloWorld')->when(String('Spanish'))->thenReturn('Hola Mundo');
$Mockify->mockStatic('FakeStaticTools::ReturnHelloWorld')->when(String('Esperanto'))->thenReturn('Saluton mondon');
$SUT = $Mockify->getMockObject();
is($SUT->useStaticFunction('German'), 'German: Hallo Welt',"$SubTestName - prove full path call- german");
is($SUT->useImportedStaticFunction('German'), 'German: Hallo Welt',"$SubTestName - prove that the same mock can handle also the imported call - german");
is($SUT->useStaticFunction('Spanish'), 'Spanish: Hola Mundo',"$SubTestName - prove full path call - spanish");
is($SUT->useImportedStaticFunction('Spanish'), 'Spanish: Hola Mundo',"$SubTestName - prove imported call - spanish");
}
# With this approach it also is not binded anymore to the scope. The Override stays with the mocked object
is($SUT->useStaticFunction('Esperanto'), 'Esperanto: Saluton mondon',"$SubTestName - prove full path call, scope independent- german");
is($SUT->useImportedStaticFunction('Esperanto'), 'Esperanto: Saluton mondon',"$SubTestName - prove that the same mock can handle also the imported call, scope independent - german");
}
The system under test is not being called directly. It is being instantiated and retrieved through Mockify, which erodes confidence in the integrity of the test.
It should look something like this instead:
sub test_mockStatic {
my $self = shift;
my $SubTestName = (caller(0))[3];
{
my $Mockify = Test::Mockify->new('FakeStaticTools',[]);
$Mockify->mockStatic('ReturnHelloWorld')->when(String('German'))->thenReturn('Hallo Welt');
$Mockify->mockStatic('ReturnHelloWorld')->when(String('Spanish'))->thenReturn('Hola Mundo');
$Mockify->mockStatic('ReturnHelloWorld')->when(String('Esperanto'))->thenReturn('Saluton mondon');
is(FakeModulStaticInjection->useStaticFunction('German'), 'German: Hallo Welt',"$SubTestName - prove full path call- german");
is(FakeModulStaticInjection->useImportedStaticFunction('German'), 'German: Hallo Welt',"$SubTestName - prove that the same mock can handle also the imported call - german");
is(FakeModulStaticInjection->useStaticFunction('Spanish'), 'Spanish: Hola Mundo',"$SubTestName - prove full path call - spanish");
is(FakeModulStaticInjection->useImportedStaticFunction('Spanish'), 'Spanish: Hola Mundo',"$SubTestName - prove imported call - spanish");
}
# With this approach it also is not binded anymore to the scope. The Override stays with the mocked object
is(FakeModulStaticInjection->useStaticFunction('Esperanto'), 'Esperanto: Saluton mondon',"$SubTestName - prove full path call, scope independent- german");
is(FakeModulStaticInjection->useImportedStaticFunction('Esperanto'), 'Esperanto: Saluton mondon',"$SubTestName - prove that the same mock can handle also the imported call, scope independent - german");
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.