How to mock a class method when unittesting in Raku

痴心易碎 提交于 2020-08-06 10:26:31

问题


Suppose I have a class like this:

class MyClass {
    method data-is-valid {
        return self!get-data ~~ m{^From};
    }

    method !get-data {
        return 'From Internet';
    }
}

where !get-data method gets some data from Internet.

Is it possible to mock that method so that it returns my own hardcoded data so I can test the module without connecting to the Internet?

Ideally, the solution should not modify the definition of the class in any way.

NOTE: A similar question exists regarding unittesting subroutines of modules.


回答1:


I would first refactor to pull the fetching logic out to a different object, and make MyClass depend on it:

class Downloader {
    method get-data {
        return 'From Internet';
    }
}

class MyClass {
    has Downloader $.downloader .= new;

    method data-is-valid {
        return $!downloader.get-data ~~ m{^From};
    }
}

This is an example of dependency inversion, which is a helpful technique for making code testable (and tends to make it easier to evolve in other ways too).

With this change, it is now possible to use the Test::Mock module to mock Downloader:

use Test;
use Test::Mock;

subtest 'Is valid when contains From' => {
    my $downloader = mocked Downloader, returning => {
        get-data => 'From: blah'
    };
    my $test = MyClass.new(:$downloader);
    ok $test.data-is-valid;
    check-mock $downloader,
        *.called('get-data', :1times);
}

subtest 'Is not valid when response does not contain From' => {
    my $downloader = mocked Downloader, returning => {
        get-data => 'To: blah'
    };
    my $test = MyClass.new(:$downloader);
    nok $test.data-is-valid;
    check-mock $downloader,
        *.called('get-data', :1times);
}



回答2:


You probably want to take a look at Test::Mock. From its SYNOPSIS:

use Test;
use Test::Mock;

plan 2;

class Foo {
    method lol() { 'rofl' }
    method wtf() { 'oh ffs' }
}

my $x = mocked(Foo);

$x.lol();
$x.lol();

check-mock($x,
    *.called('lol', times => 2),
    *.never-called('wtf'),
);



回答3:


Hi @julio i suggest you take a look at the wrap function for routines, this should do what you need... https://docs.raku.org/language/functions#Routines ... this includes use soft; pragma to prevent inlining




回答4:


Probably the best idea would be to refactor the code (see Jonathan's answer)

However, If you cannot for some reason, there are still alternatives:

If the method is public you can simply create a subclass and override the method.

For example:

use Test;

class MyClass {
    method data-is-valid {
        return self.get-data ~~ m{^From};
    }

    method get-data {
        return 'From Internet';
    }
}

class MyClassTester is MyClass {
    method get-data {
        return 'Foobar';
    }
}

my MyClassTester $class = MyClassTester.new;

nok $class.data-is-valid, 'Mocked class has invalid data';

done-testing;

If the method is private, you can use wrap as stated on p6steve's answer. However you need introspection in order to modify the private method.

It can be done like this:

use Test;

class MyClass {
    method data-is-valid {
        return self!get-data ~~ m{^From};
    }

    method !get-data {
        return 'From Internet';
    }
}

my $class = MyClass.new;

my Method:D $get-data = $class.^find_private_method: 'get-data';

$get-data.wrap: { 'Foobar' };

nok $class.data-is-valid, 'Mocked class has invalid data';

done-testing;


来源:https://stackoverflow.com/questions/63042080/how-to-mock-a-class-method-when-unittesting-in-raku

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!