The 2002 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 14th day of Advent my True Language brought to me..
Test::MockObject

When you're writing tests for your new module, one of the hardest things is creating the environment that it needs to run in. Often you'll find that your object will rely on a database connection, or a vast object structure that you can't easily create in the test suite.

Other times you'll find that your class's job is to interact with another object which will change it's internal state in some way - and that it's really complex to test without understanding how the other object works - not fun if you haven't written it yourself. And worse - if anyone ever changes the way the internals of that object works then your test suite will break.

It's all really messy. That's where Test::MockObject comes in. It allows you to quickly declare objects that have methods. These can be set up to return various results when they're called, and you can query the mocked object to see what methods have been called, allowing it to be used in both the above situations.

Okay, it's time for another silly Buffy based example.

Imagine we have a object named Doyle. This object listens on a socket across the Internet for messages from another server call TPTB (short for 'The Powers That Be'.) When it receives messages it creates instances of the Monster class and stores them. There's another class called "Angel" that is responsible for clearing up these monsters whenever something calls it's need_help method by calling Doyle's get_monster method and dealing with the results one by one.

Though much toil and trouble, pretend we've managed to fully test our Doyle object and we're sure it's working. And we're sure that the Monsters class is working fine too. It's the Angel class we're worried about - we've never written tests for it and sometimes it has a nasty habit of going bad.

First up, let's look at the code for Angel, so we know what we're talking about:

  package Angel;
  # turn on Perl's safety features
  use strict;
  use warnings;
  # the constructor
  sub new
  {
    my $class = shift;
    my $self = bless {}, $class;
    $self->{fought} = [];
    return $self;
  }
  # accessor method people can use to set the helper classes
  # that we use to get our monsters
  sub set_doyle
  {
    my $self       = shift;
    $self->{doyle} = shift;
  }
  sub set_cordy
  {
    my $self       = shift;
    $self->{cordy} = shift;
  }
  # main method, calls doyle/cordy and asks about monsters
  # and then deals with them
  sub need_help
  {
    my $self = shift;
    # find out where the monsters are
    my @monsters;
    if ($self->{doyle}->alive)
    {
      @monsters = $self->{doyle}->get_monsters();
    }
    else
    {
      @monsters = $self->{cordy}->get_monsters();
    }
    # deal with each monster in turn
    foreach my $monster (@monsters)
    {
      # record it as a monster we've fought
      push @{$self->{fought}}, $monster;
      # kill it
      if ($monster->type eq 'vampire')
      {
        $monster->stake;
      }
      else
      {
        $monster->stab;
      }
    }
  }
  # return a list of things we fought
  sub fought
  {
    my $self = shift;
    return @{$self->{fought}}
  }
  1;

So we start the test script for the Angel class like so

  #!/usr/bin/perl
  # we're running tests, though we don't know how many
  # just yet change this when we know how many
  use Test::More qw(no_plan);
  # turn on Perl's safety features
  use strict;
  use warnings;
  # test that the Angel class compiles
  BEGIN { use_ok "Angel"; };
  # check that if we call the constructor we get a
  # proper object back again
  my $angel = Angel->new();
  isa_ok($angel,"Angel");

Now, we can't just create a Doyle object as that requires a live connection to the TPTB server, and we'd have to convince that to generate Monster events when we're testing and we can't determine when that's going to happen. So what we do is we fake it.

  # create a fake doyle, and tell Angel where to find it
  use Test::MockObject;
  my $doyle = Test::MockObject->new();
  $angel->set_doyle($doyle);

Now we need to create responses. Since the system is designed to be able to function even if we our Doyle fails, our real Doyle class has an alive method that returns true if and only if it is working and has a connection to TPTB. Since this method is called by our Angel code our fake mocked version needs to have a method like that too.

  # add a 'alive' method to the doyle object that
  # always returns true.
  $doyle->set_true('alive')

So now whenever we call

  $doyle->alive();

it'll return true, so our Angel code will run okay. Of course later on when we want to test the fail over we'll probably do something like.

  $doyle->set_false('alive');

We can also easily mock other methods to always return whatever we want, for example:

  $doyle->set_always('unique_object_id', 'Allen Francis Doyle');

So, back to the tests. We simply want our mock object to return some new monsters each time its get_monsters method is called by the Angel object. To do that we can use the mock method that allows us to specify a code ref that is called every time an attempt to call a particular method is made.

  # every time 'get_monsters' is called return two new monsters
  use Monster;
  $doyle->mock('get_monsters',
               sub { return (Monster->new(), Monster->new()) });
  # call the need_help method a number of times
  $angel->need_help;
  $angel->need_help;
  $angel->need_help;
  # angel should have fought 3 x 2 monsters by now
  is($angel->fought, 6, "fought right number of monsters");

Checking methods have been called

So we've satisfied ourselves that the Angel object is indeed getting monsters back from the Doyle object. However, we haven't checked if the Angel object is attempting to kill the monsters properly.

This is an example of the problem I alluded to in the foreword...how can we actually tell if a method has been called on an object used by our class or not? The answer is simple, we mock that object as well since Test::MockObject objects have the exceedingly handy feature of being able to keep track of how many times each of their methods have been called.

  # create a mock monster, which is a vampire and has stake
  # and stab methods
  my $monster = Test::MockObject->new();
  $monster->set_always(type  => 'vampire');
  $monster->set_always(stake => 'foo');
  $monster->set_always(stab  => 'foo');
  # create a second mock monster, which isn't a vampire and
  # has stake and stab methods
  my $monster2 = Test::MockObject->new();
  $monster2->set_always(type  => 'demon');
  $monster2->set_always(stake => 'foo');
  $monster2->set_always(stab  => 'foo');
  # reconfigure doyle to return $monster the first time
  # get_monsters is called and $monster2 the second
  $doyle->set_series('get_monsters', $monster, $monster2);

Now when we call need_help on the Angel object it should get a vampire the first time and a demon the second, and call the stake method for the former and the stab for the latter.

  # deal with both monsters
  $angel->need_help;
  $angel->need_help;
  ok($monster->called('stake'),   "vampire staked");
  ok(!$monster->called('stab'),   "vampire not stabbed");
  ok(!$monster2->called('stake'), "demon not staked");
  ok($monster2->called('stab'),   "demon stabbed");

Further Techniques

In addition to the techniques I've described here, Test::MockObject provides many other useful methods. It has other more flexible ways to set what methods return, and many more interesting ways to query exactly what methods have been called, all of which are documented very well in the perldoc for the module.

  • The Perl.com article on Test::MockObject
  • Test::More