I've been trying out Moose on a hobby project, and I'd like to get some feedback on the way I'm unit testing my roles. I've ended up writing a couple of role/mock test support modules, and I suspect I've reinvented some wheels in the process.
How do you unit test your roles ? In my app, roles define interfaces and classes that do the roles implement the interfaces. I use method modifiers in the roles, for example: # In ZF::Role::Foo before 'foo' => sub { my $self = shift; $self->has_foo_prerequisite or $self->setup_foo_prerequisite; }; For each such role I have a mock implementation. The mock implementations are useful to test code that needs an object that does the role, and they also contain unit tests to ensure that the role code itself is doing the right thing: # In ZF::Mock::Foo sub mock_testcount_foo { 1 } sub foo { my $self = shift; if (my $t = $self->mock_test) { ok $self->has_foo_prerequisite, 'role fixed prereq'.$t->tns; } ... } The mock_test() method is inherited from ZF::Mock, and it returns false if this mock object is not in run-internal-unit-tests mode, otherwise it returns an object. $t->tns returns a Test Name Suffix. The ZF::Mock baseclass adds a mock_method_plan attribute, which is used to declare how many times each method of the mock class is expected to be called. In combination with mock_testcount_foo() type per-method test count declarations, this makes it possible to calculate the number of tests that will be run. The test scripts are supported by a somewhat Test::Class-like module, which handles setting up mock objects with their method plans: use Test::More; use ZF::Test::TestSet; use ZF::Mock::Foo; testset 2, 'basic', ['ZF::Mock::Foo', {foo => 2}], sub { my $foo = mockobj(bar => 'yes'); is $foo->foo, 'foo', 'foo is foo'; is $foo->foo, 'foo', 'foo is still foo'; }; ... and that's a complete test script. The support modules calculate the test plan and run the tests in an END block. The mockobj() call consumes the class name and method plan from the arrayref, and returns the mock object. Those have to be declared outside the testset sub so that they are available for calculating the test plan before the sub is executed. I find that this approach eliminates the pain of debugging a bad test count, since the support modules add tests to check that each testset runs the right number of tests, that each mock method is called the right number of times and that the method runs the right number of tests on each call. Sometimes the method call profile varies with the test input, and calculating the method plan is a bit of a pain. I'm half convinced that it's worth it, since tests that make strong assertions about the set of method calls that reach the implementing class are a good thing when testing roles that use "around", right ? Since testset is a runtime thing, I can call it in a loop if the method plan varies with test input: foreach my $input (keys %input_data) { testset, 1, "foo $input", ['ZF::Mock::Foo', compute_method_plan($input)], sub { my $tns = shift; my $foo = mockobj(stuff => $input_data{$input}); 1 while $foo->munge; is $foo->foo, 'foo', 'foo is foo'.$tns; }; } --ZF