Testing HTTP client code with Plack
I've been using Perl for a while now, and while much of my day job has revolved around legacy Perl, continuous development and refactoring has helped to introduce more and more modern Perl into my daily work. It was one of my more recent adventures that led me to write this, my first Perl blog post.
I'd been working on a small Plack-based application which grabs image files over HTTP for processing. To make testing easier, I'd written the class so that I could inject the HTTP client as a dependency (the default is HTTP::Tiny). But I then wondered if rather than creating a mock HTTP client for testing, it would be simpler and more effective to fire up a simple HTTP server instead. I thought I'd share my solution here because I've been so impressed with all the modules that made it (and the project as a whole) possible.
I was aware of Plack::Test, and how it is possible to specify the backend against which the tests should be run. I had also seen Plack::App::File, which serves static files from a local directory. After a little research, the solution turned out to be really simple, and the parts that do the real work require just a couple of lines of code:
my $static = Plack::App::File->new(root => "$Bin/static/")->to_app;
my $server = Plack::Test::Server->new($static);
With that in place, I could create sample files in the test directory and have the server return them directly. Here's a contrived example to demonstrate:
use Test::Modern;
use FindBin qw($Bin);
use Plack::App::File;
use Plack::Test::Server;
use HTTP::Tiny;
use URI;
# Create a PSGI application that serves static files from within
# the t/static directory:
my $static = Plack::App::File->new(root => "$Bin/static/")->to_app;
# Create an HTTP server (via Test::TCP):
my $server = Plack::Test::Server->new($static);
# Everything else has been contrived for demonstration purposes:
my $http = HTTP::Tiny->new;
my $uri = URI->new;
$uri->scheme('http');
$uri->host('localhost');
$uri->port($server->port);
subtest 'files that exist' => sub {
$uri->path('path/to/file/that/exists');
my $res = $http->get($uri);
# Requires t/static/path/to/file/that/exists
ok($res->{success}, 'file exists');
is($res->{content}, '...', 'file content ok');
};
subtest 'files that do not exist' => sub {
$uri->path('path/to/file/that/does/not/exist');
my $res = $http->get($uri);
ok(!$res->{success}, 'file does not exist');
};
done_testing;
Cool!
You can also use Test::LWP::UserAgent to mock a server that can return arbitrary responses, so you can test how your client handles the weird edge case errors.