Not so new shiny things: LWP::Protocol::PSGI

Too long/didn't read: Don't use Test::WWW::Mechanize::PSGI or similar, use LWP::Protocol::PSGI. I wish I'd found out about this module when it was first released in 2011 rather than in 2015. Which is why this article is here. LWP::Protocol::PSGI is great work and needs to be more widely known.

Since we moved away from monolithic web applications deeply embedded into the web server via the web framework movement and PSGI, now around a decade ago, a common way of testing things has been with modules like Catalyst::Test, Test::WWW::Mechanize::PSGI, Dancer2::Test and similar. These work by mocking the http request and response layers, with the mocked layers talking to an object or a subroutine reference rather than a web server. Which is fine, except, say you want to test both against your application code and your web server using the same test suite. You end up having to do fairly nasty things to decide if you are dealing with a mocked http request/response layer talking to a coderef, or a real one talking to a web server.

Fortunately LWP::Protocol::PSGI eliminates this entire problem.

Here's a sample, untested, but based on real stuff in my archives:

#!/usr/bin/env perl                                                                                      
use warnings;                                                                                            
use strict;                                                                                              
use MyApp;                                                                                               
use Test::WWW::Mechanize;                                                                                

if ($ENV{LIVE_TESTING}) {                                                                                
    require LWP::Protocol::PSGI;                                                                         
    my $app = MyAPP->as_psgi; # or whatever the call is for your app                                     
    LWP::Protocol::PSGI->register($psgi_app);                                                            
}                                                                                                        

my $mech = Test::WWW::Mechanize->new;                                                                    
$mech->get_ok('http://localhost:5000');                                                                  
# and so on.

The register call above routes all urls through the psgi application. It's possible to register different urls to different applications, and have some calls use a real web server. See the LWP::Protocol::PSGI for documentation.

Aside from swapping out your app as a code reference/real server trivially, another use case is for complicated architecture that's very difficult to test without applying a pile of system administration/automation to your dev box. Here's a real world example I dealt with a while ago:

  1. Hit a Catalyst app.
  2. Catalyst app makes calls to a different mod_perl application.
  3. Hit same catalyst app as different user
  4. Validate original data via catalyst app and mod_perl app talking to each other.
  5. Hit external script that talks to the mod_perl application in a job queue.
  6. Confirm via mod_perl app that all the required things should have happened.

Once the mod_perl application had a working psgi compatibility
layer
it was relatively straightforward to have LWP::Protocol::PSGI manage the entire web application deployment' layer for testing:

  1. Register Catalyst app with LWP::Protocol::PSGI
  2. Do the same for the mod_perl/psgi app on a different uri
  3. The original steps 3 and 4 are now pretty trivial to write tests for.
  4. Convert the external script to the
    modulino pattern so it can be instantiated inline of the test script.
  5. The original step 6 is now pretty trivial to test too.

So we've gone from a test environment that was very hard to test, and needed lots of fiddly interprocess communication, to something that's tested in a single perl process. When you have complicated stuff like this bugs do come up, and pipelining everything into a single process makes it much easier to work out what's going on.

1 Comment

[Note also my Plack::Middleware::MockProxyFrontend, which is the same idea in a different context: instead of making every LWP request go to your PSGI app, it makes every request from your browser go to your PSGI app – no matter which host the browser thinks it’s speaking to.]

Leave a comment

About kd

user-pic Australian perl hacker. Lead author of the Definitive Guide to Catalyst. Dabbles in javascript, social science and statistical analysis. Seems to have been sucked into the world of cloud and devops.