Back on Day 9 we discussed testing and especially Test::Mojo. Today I want to just briefly talk about some practical things that can come up when testing real world applications. Once again the discussion will be motivated by the Wishlist application that we've been developing these past few days.

Configuration Overrides

In the Day 9 article I mentioned that the Test::Mojo constructor could be passed configuration overrides. In this example we can see how that override lets us ensure that we are testing on a fresh and isolated database.

use Mojo::Base -strict;

use Test::More;
use Test::Mojo;

my $t = Test::Mojo->new(
  'Wishlist',
  {database => ':temp:'}
);

my $model = $t->app->model;
is_deeply $model->list_user_names, [], 'No existing users';

my $user_id = $model->add_user('Zoidberg');
ok $user_id, 'add_user returned a value';
my $user = $model->user('Zoidberg');
is_deeply $user, {
  id => $user_id,
  name => 'Zoidberg',
  items => [],
}, 'correct initial user state';
is_deeply $model->list_user_names, ['Zoidberg'], 'user in list of names';

my $item_id = $model->add_item($user, {
  title => 'Dark Matter',
  url   => 'lordnibbler.org',
});
ok $item_id, 'add_item returned a value';
$user = $model->user('Zoidberg');
is_deeply $user, {
  id => $user_id,
  name => 'Zoidberg',
  items => [
    {
      id => $item_id,
      purchased => 0,
      title => 'Dark Matter',
      url   => 'lordnibbler.org',
    },
  ],
}, 'correct initial user state';

done_testing;

t/model.t

When called this way the passed hashref is loaded into the configuration rather than any configuration file. Because of the way the sqlite attribute initializer was coded, the Mojo::SQLite special literal :temp: is passed through unchanged. Now I would never suggest that you write any code into your application that is specific to testing, however it is entirely reasonable to code around special literals that someone might need. You could of course start a Wishlist server using a temporary database.

SQLite's in-memory (and Mojo::SQLite's on-disk temporary) databases are really handy for testing because they are automatically testing in isolation. You don't have to worry about overwriting the existing database nor clearing the data at the end of your test. Further, you can run your tests in parallel to get a nice speedup in large test suites.

For databases that require a running server you have to be a little more careful, however isolated testing is still very possible. For example, in Mojo::Pg you can set a search_path which isolates your test.

my $t = Test::Mojo->new(
  'MyApp',
  {database => 'postgresql:///test?search_path=test_one'}
);
$pg->db->query('drop schema if exists test_one cascade');
$pg->db->query('create schema test_one');
...
$pg->db->query('drop schema test_one cascade');

You might have to be careful about when the migration happens too (ie disable auto_migrate and run it manually). Also this will only isolate the tests per-name, here test_one. Therefore I recommend you name the path for the name of the test file, this should be both descriptive and unique. And you have to clean up after yourself otherwise the next time the test is run it will be affected by the remaining data.

Mocking Helpers

If you have done any testing you've probably dealt with mocking, but if you haven't, mocking is the act of replacing functionality from essentially unrelated code with test-specific code. Doing this lets you test one section of code (called a unit) in isolation from others.

Everyone has their favorite mock library. There are so many tastes and styles that in the end Many people make their own, including yours truly. Of course you can use those libraries in Mojolicious when appropriate. As I mentioned before you can also mock out services by attaching tiny Mojolicious applications to a UserAgent's server attribute or making an entire external service as Doug showed us on Day 8.

In some cases however, the natural place to mock is in the place of a helper. When you think about it, this is actually rather obvious since helpers are often the glue used in Mojolicious applications to combine disparate code, like models or in this case the LinkEmbedder.

To test this we could actually do any of the mentioned options from mocking LinkEmbedder->get to attaching a mock service. That said it is sufficient here to just replace the helper, which is as easy as assigning over it.

use Mojo::Base -strict;

use Test::More;
use Test::Mojo;

my $t = Test::Mojo->new(
  'Wishlist',
  {database => ':temp:'}
);

{
  package Local::MockLink;
  use Mojo::Base -base;
  has [qw/title url html/];
}

# "mock" the link helper
my ($url, $data);
$t->app->helper('link' => sub {
  (undef, $url) = @_;
  return Local::MockLink->new($data);
});

$data = {
  title => 'Cool Beans',
  url => 'coolbeans.notasite',
  html => '<p>Some really Cool Beans</p>',
};

$t->get_ok('/add?url=coolbeans.notasite')
  ->status_is(200)
  ->text_is('#item-detail p', 'Some really Cool Beans')
  ->element_exists(
    'form input[type="hidden"][name="title"][value="Cool Beans"]'
  )
  ->element_exists(
    'form input[type="hidden"][name="url"][value="coolbeans.notasite"]'
  );

is $url, 'coolbeans.notasite', 'correct site was requested';

done_testing;

t/embed.t

Because the template expects the result to be an object we have to build a tiny class to contain our mock results. Also whenever you are mocking, it is important to check the input your mock received as well as the results that the calling code derives from your mock return value.

In the test you can also see some examples of how to use selectors to test for both text and attribute values. The text test is especially important because it shows that the html value that I got back from the LinkEmbedder isn't being escaped by the template and will render as HTML to the client.

A few more tests and some documentation and our application will really be taking final shape!

Image by Hawaii Volcano Observatory, USGS - Kilauea image archive for June-October 2009: see entry for 26 June 2009., Public Domain.

Tagged in : advent, mocking, testing, wishlist

author image
Joel Berger

Joel has Ph.D. in Physics from the University of Illinois at Chicago. He an avid Perl user and author and is a member of the Mojolicious Core Team.