Testing private methods in Raku

A fresh cup of tea and @Julio's and @JJ's answers inspired the following:

class SomeClass { method !private ($foo) { say $foo } }

use MONKEY-TYPING; augment class SomeClass { trusts GLOBAL }

my SomeClass $some-class = SomeClass.new;

$some-class!SomeClass::private(42); # 42

My solution tweaks the class using monkey typing. Monkey typing is a generally dodgy thing to do (hence the LOUD pragma). But it seems tailor made for a case just like this. Augment the class with a trusts GLOBAL and Bob's your Uncle.

Raku requires the SomeClass:: qualification for this to work. (Perhaps when RakuAST macros arrive there'll be a tidy way to get around that.) My inclination is to think that having to write a class qualification is OK, and the above solution is much better than the following, but YMMV...

Perhaps, instead:

use MONKEY-TYPING;
augment class SomeClass {
  multi method FALLBACK ($name where .starts-with('!!!'), |args) {
    .(self, |args) with $?CLASS.^find_private_method: $name.substr: 3
  }
}

and then:

$some-class.'!!!private'(42); # 42

I've used:

  • A multi for the FALLBACK, and have required that the method name string starts with !!!;

  • A regular method call (. not !);

  • Calling the method by a string version of its name.

The multi and !!! is in case the class being tested already has one or more FALLBACK methods declared.

A convention of prepending !!! seems more or less guaranteed to ensure that the testing code will never interfere with how the class is supposed to work. (In particular, if there were some call to a private method that didn't exist, and there was existing FALLBACK handling, it would handle that case without this monkey FALLBACK getting involved.)

It should also alert anyone reading the test code that something odd is going on, in the incredibly unlikely case that something weird did start happening, either because I'm missing something that I just can't see, or because some FALLBACK code within a class just so happened to use the same convention.


Besides using introspection, you can try and use a external helper role to access all private methods and call them directly. For instance:

role Privateer {
    method test-private-method ( $method-name, |c  ) {
    self!"$method-name"(|c);
    }
}

class Privateed does Privateer {
    method !private() { return "⌣"  }
}

my $obj = Privateed.new;
say $obj.test-private-method( "private" );

The key here is to call a method by name, which you can do with public and private methods, although for private methods you need to use their special syntax self!.


This can be done using introspection.

Consider this is the class you want to test:

class SomeClass {
    has Int $!attribute;

    method set-value(Int $value) returns Nil {
        $!attribute = $value;
        return;
    }

    method get-value returns Int {
        return $!attribute;
    }

    # Private method
    method !increase-value-by(Int $extra) returns Nil {
        $!attribute += $extra;
        return;
    }
}

You may create a test like this:

use Test;
use SomeClass;

plan 3;

my SomeClass $some-class = SomeClass.new;
my Method:D $increase-value = $some-class.^find_private_method: 'increase-value-by';

$some-class.set-value: 1;
$increase-value($some-class, 4);
is $some-class.get-value, 5, '1+4 = 5';

$increase-value($some-class, 5);
is $some-class.get-value, 10, '5+5 = 10';

my SomeClass $a-new-class = SomeClass.new;
$a-new-class.set-value: 0;
$increase-value($a-new-class, -1);
is $a-new-class.get-value, -1, '0+(-1) = -1; The method can be used on a new class';

done-testing;

You first create an instance of the class and the use ^find_private_method to get its private Method. Then you can call that Method by passing an instance of a class as the first parameter.

There's a more complete explanation on this answer:

How do you access private methods or attributes from outside the type they belong to?