Giter VIP home page Giter VIP logo

test-mockmodule's Introduction

Test::MockModule - mock subroutines in a module

See the LICENSE section in lib/Test/MockModule.pm for usage and distribution rights.

Installation

Run these commands in the source directory:

perl Build.PL
./Build
./Build test
./Build install

Then delete the source directory tree since it's no longer needed.

Alternatively, you can simply install the module via cpanm:

cpanm Test::MockModule

Documentation

Run perldoc Test::MockModule to read the full documentation.

test-mockmodule's People

Contributors

atoomic avatar drhyde avatar fgasper avatar geofffranks avatar gregoa avatar kentfredric avatar lancew avatar lharey avatar manwar avatar matthewhughes934 avatar mend-bolt-for-github[bot] avatar nima-fs avatar oalders avatar sshine avatar stephenenelson avatar toddr avatar ziali088 avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

test-mockmodule's Issues

We should be able to mock CORE::GLOBAL without checking INC

We should not check %INC in this case

> perl -MTest::MockModule -E 'Test::MockModule->new( "CORE::GLOBAL" )'
Can't locate CORE/GLOBAL.pm in @INC (you may need to install the CORE::GLOBAL module)

So we could easily mock CORE functions like system for example.

done_testing requires Test::More 0.88

The use of done_testing in the test suite requires Test::More 0.88 or later, but Build.PL is only asking for version 0.45 or later, which can result in test suite failures when installing on older systems.

Doc examples cause object persistence ... `original` should work even when not mocked.

An example showing how to use original ensures that the object never goes out of scope, meaning the package is never restored.

Consider this snippet from the docs:

$mock->redefine("get_path_for", sub {
        my $path = shift;
        if ( $path && $path eq "/a/b/c/d" ) {
                # only alter calls with path set to "/a/b/c/d"
                return $mock->original("get_path_for")->("/my/custom/path");
        } else { # preserve the original arguments
                return $mock->original("get_path_for")->(@_);
        }
});

The subref used to redefine get_path_for closes over $mock. This means that even if $mock goes out of scope in the test, the subref holds onto a reference for it, and is never removed. $mock would need to be weak inside the subref, but I don't know how to do that.

use Test::MockModule;
use REST::Client;
use feature 'say';

{
    my $mock = Test::MockModule->new('REST::Client');
    $mock->mock('GET', sub {
        say "GET called";
        return $mock->original('GET')->(@_);
    });
    REST::Client->new(host => 'http://example.com')->GET('/test');
}

REST::Client->new(host => 'http://example.com')->GET('/test');
▶ perl test.pl
GET called
GET called

My instinct would be to store the original before mocking, and close over that, but this causes an error.

    my $orig = $mock->original('GET');
    $mock->mock('GET', sub {
        say "GET called";
        return $orig->(@_);
    });

It is possible to do it this way, but it does not allow for inheritance:

    my $orig = \&REST::Client::GET;
    $mock->mock('GET', sub {
        say "GET called";
        return $orig->(@_);
    });
▶ perl test.pl
GET called

Ideally, original should not consider it an error for the function not to be mocked.

Lexical strict mode for mockmodule makes it difficult to have global strict mode.

In #53, an issue was raised that a global strict mode was a problem for some Mojo tests. The pull request submitted in #54 got rid of global strict and replaced it with a lexical only strict mode for Test::MockModule.

@DrHyde, We needed this global strict mode which we used in a policy to detect if people were failing to set strict mode for their import.

Looking at the pull request, I want to suggest an alternative. I would like to put back the global strict mode but honor a lexical "nostrict" mode when specified. Would this work for you @DrHyde ?

new does not return a new object

If you call new twice with the same package name, you get the same object. This makes the entire thing useless.

We have a test library full of helper functions that set up common mocks. In order for this to work at all it must hang onto a reference to the mocked object.

If a test then later tries to mock the same module, it receives the same object! Then, it sets up a mock on that object. It is perfectly reasonable to expect that when that object goes away, the mocked behaviour goes away.

It is not reasonable to expect that the object will not go away after all!

package My::Mock::Helpers;
use Test::MockModule;
my %mocks;

sub mock_external_api_get {
  return if $mocks{mock_external_api};

  my $mock_api = Test::MockModule->new('My::API::Client');
  $mock_api->mock('get_object', sub { return My::API::Client::Object->new_defaults });

  $mocks{mock_external_api} = $mock_api;
}
# test file
use Test::More;
use Test::MockModule;
use Scalar::Util qw(weaken);
use My::DB::Schema;
use My::Mock::Helpers;

My::Mock::Helpers::mock_external_api_get;

subtest "set_object is called" => sub {
  my $mock_api = Test::MockModule->new('My::API::Client'); # NOT MY OBJECT!
  my $mock_api_closure = $mock_api;
  weaken $mock_api_closure;
  $mock_api->mock('set_object', sub {
    # Tests that object 1 is saved
    is $_[1]->id, 1, "Object 1 saved";
    $mock_api_closure->original('set_object')->(@_);
  });
  
  # something that eventually calls set_object
  My::DB::Schema->find_object(id => 1)->save;
};

# ... later ...

My::DB::Schema->find_object(id => 2)->save;

The above example causes a test failure. In the example, My::DB::Schema uses objects that, when save is called on them, send themselves to this example API, by means of My::API::Client.

My::API::Client is also set up to mock GET requests to always return some example default object.

Because Test::MockModule does not return a new object when new is called, the developer who wrote the test mistakenly believes that their subtest will constrain the mocked set_object to the scope of that subref. However, this mock is permanent.

Later in the test, save is called on another object, and the test fails, because a mock that should have gone away has not gone away.

new should always return a new object, so that developers can be confident that it will go out of scope when they think it will.

Wrong usage in synopsis of POD

The second example in the Synopsis part of the POD reads

my $mock = Test::MockModule('Foo');

which should be

my $mock = new Test::MockModule('Foo');

import() should check for unknown imports

We had an issue with using the strict option.
In one of CI systems we have Test::MockModule 0.13, and the other CI and developers have the newest version.
Because the import function does not check if unknown symbols are imported, we never got a warning that strict is not supported in this version.
Additionally I think that this code could be improved:

$STRICT_MODE = 1 if ( grep { $_ =~ m/strict/i } @args );

It allows something like use Test::MockModule 'noSTRICT for example.
Is there a reason not just allowing strict and die for anything else?

License mismatch

lib/Test/MockModule.pm states:

You may distribute under the terms of either the GNU General Public License or
the Artistic License, as specified in the Perl README file.

LICENSE reads:

This is free software, licensed under:

  The GNU General Public License, Version 3, June 2007

May I know what of these two statements are true? Perl is licensed under GNU GPLv1 or later, or under Artistic License.

Add a function to automatically mock all subroutine with a no-op or die function.

Sometimes when I mock I forgot to mock some subroutine and the unit test calls the real module, causing hard-to-debug tests. It would be nice to have a function that can automatically mock all subroutines of a module with a no-op implementation or sub { die }, so if we forgot to mock a subroutine, the whole test will fail early.

Something like

my $module = Test::MockModule->new('Foo');
$module->auto_mock_all();

Foo->bar(); # dies, or simply no-op

[Feature Request] Allow chaining of mocking functions after new

The idea is to be able to do something like this

# use redefine after a new
my $mock = Test::MockModule->new( 'MyModule' )->redefine( myfunction => 42 );

# which could also allow something like this
my $mock = Test::MockModule->new( 'MyModule' )
                     ->redefine( myfunction => 42 )
                     ->define( missing function => sub { ... } );

Same with all other functions like define, mock,...

As it stands redefine just returns the _mock which does not have a clear return value, so this sounds doable with a simple patch.

I think the implementation is pretty easy, can also add some tests...
probably more time updating the doc than anything else.

@geofffranks would you be open to something like this? if so I can provide a prototype

Strict mode should be lexically scoped

If I use T::MM in two places at the same time, and turn on strict mode in one, my mock()s start failing in the other. There are legitimate reasons to still use mock(), such as where the sub being mocked is inserted in crazy magical places by Mojo, or if it is used in some third-party libs that you can't change.

Mocking exported functions

Test::MockModule does not work very well with exported functions. Consider the following example:

#!/usr/bin/perl

use strict;
use warnings;

use POSIX;

use Test::MockModule;
my $mock;
BEGIN {
    $mock = Test::MockModule->new('POSIX');
    # $mock = Test::MockModule->new('main', no_auto => 1);
    $mock->mock(strftime => sub { warn "mocked strftime" });
}

strftime("%F %T", localtime);

$mock->mock(strftime => sub { warn "re-mocked strftime" });

strftime("%F %T", localtime);

__END__

In this case strftime is not mocked. One has to know that exported functions are imported into the current namespace, so a possible workaround is to use

    $mock = Test::MockModule->new('main', no_auto => 1);

Another possible workaround is to move the use POSIX statement after the BEGIN block. In this case the replaced strftime is used. But again, re-mocking is not possible here.

I am not sure if Test::MockModule can be improved here, but at least the documentation should contain some lines on this topic.

Request: mock() variant that die()s if the original is undefined

Generally when I see this module used it’s to redefine a method; if the original method doesn’t exist, then the test is testing itself rather than the production code.

It would be great to have:

$mocker->redefine( NAME, CODEREF )

… identical to mock() but with a die() if the original function doesn’t exist. That way the test can complain if the object internals have changed such that the test is no longer flexing the code properly.

Thoughts?

redefine() assumes object implementation is a hashref

> perl -Mwarnings -MTest::MockModule -e'package Foo; sub DESTROY { print 234 if $_[0][1] }; package main; $INC{"Foo.pm"} = __FILE__; my $mm = Test::MockModule->new("Foo"); $mm->redefine( what => sub {} )'
	(in cleanup) Not an ARRAY reference at -e line 1.
Foo::what does not exist! at -e line 1.

Can't mock implementations of abstract methods required by a Moose role

If you have a Moose class which consumes a role, and that role in turn requires that the class implement a method foo then you can't use Test::MockModule to over-ride the class's implementation. Attempting to do so can result in errors like:

"Invalid implementation class MyApp::SomeClass: 'MyApp::SomeRole' requires the method 'foo' to be implemented by 'Moose::Meta::Class::ANON::SERIAL::8'. If you imported functions intending to use them as methods, you need to explicitly mark them as such, via Moose::Meta::Class::ANON::SERIAL::8->meta->add_method(foo => ..."

As a work-around, instead of doing this in my tests:

my $mockery = Test::MockModule->new('MyApp::Class');
$mockery->mock( foo => sub { 0 });

I am doing this:

MyApp::Class->meta->add_method(totes_not_a_real_sub => sub { 0 });
my $mockery = Test::MockModule->new('MyApp::Class');
$mockery->mock( foo => \&MyApp::Class::totes_not_a_real_sub);

but obviously I'd prefer to have Test::MockModule do the nasty Moose dance for me automagically. Would you accept a patch for that?

Prototype Mismatches throws warnings

One liner:

$>perl -Mstrict -w -MTest::MockModule -MProc::FastSpawn -E'my $m = Test::MockModule->new("Proc::FastSpawn"); $m->redefine("spawn_open3", sub {1})'            
Prototype mismatch: sub Proc::FastSpawn::spawn_open3 ($$$$$;$) vs none at /usr/local/cpanel/3rdparty/perl/526/lib64/perl5/cpanel_lib/Test/MockModule.pm line 163.
Prototype mismatch: sub Proc::FastSpawn::spawn_open3: none vs ($$$$$;$) at /usr/local/cpanel/3rdparty/perl/526/lib64/perl5/cpanel_lib/Test/MockModule.pm line 163.

The program looks like this:

use strict;
use warnings;
use Test::MockModule;
use Proc::FastSpawn;

my $m = Test::MockModule->new("Proc::FastSpawn");
$m->redefine("spawn_open3", sub {1})'   

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.