Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a class method when unittesting in Raku

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.

like image 347
Julio Avatar asked Jul 22 '20 19:07

Julio


People also ask

Can you mock a method in a class?

Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls. Through mocking you can explicitly define the return value of methods without actually executing the steps of the method.

What does mocking a class allow you to do?

Mocking means creating a fake version of an external or internal service that can stand in for the real one, helping your tests run more quickly and more reliably. When your implementation interacts with an object's properties, rather than its function or behavior, a mock can be used.


3 Answers

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);
}
like image 129
Jonathan Worthington Avatar answered Sep 22 '22 20:09

Jonathan Worthington


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'),
);
like image 29
Elizabeth Mattijsen Avatar answered Sep 20 '22 20:09

Elizabeth Mattijsen


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

like image 35
p6steve Avatar answered Sep 23 '22 20:09

p6steve