LWP::UserAgent と LWP::Protocol::PSGI でテストを書くと楽できる話

Plack::Test + HTTP::Request::Common

世の中には Plack::Test + HTTP::Request::Common という方法もあるが、この場合ブラウザを模したようなテストを書くと意外にも破綻しやすい。とりわけセッション周りの挙動が必須になると大変な手間になる。

LWP::UserAgent + LWP::Protocol::PSGI

最近は LWP::UserAgent + LWP::Protocol::PSGI で楽をするような方法で書くようにしている。ユーザー寄りのテスト(ログイン後の処理やCSRF対策用のトークンが必須等)をわりと楽に書ける点がメリット。

subtest と scope のメリットも享受できる /zento+/ 方式が見通し良く出来そう。*1

サンプル

Some::Middleware::CSRFDefenderのテストを書くとすればこんな感じ。

use Test::More;

use LWP::UserAgent;
use LWP::Protocol::PSGI;

use Plack::Request;
use Plack::Session;
use Plack::Middleware::Session;

use Some::Middleware::CSRFDefender;

my $raw_app = sub {
    my $env = shift;
    my $ses = Plack::Session->new($env);
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
    $res->body( $ses->get("csrf_token") );
    return $res->finalize;
};

my $url = "http://localhost/";

subtest "Should not work without session middleware" => sub {
    my $app = Some::Middleware::CSRFDefender->wrap($raw_app);
    my $guard = LWP::Protocol::PSGI->register($app);

    my $res = LWP::UserAgent->new->get($url);
    is $res->code, 500;
};

subtest "Basic test cases" => sub {
    # Prepare environments for testing
    my $app = Plack::Middleware::Session->wrap(
        Some::Middleware::CSRFDefender->wrap(
            $raw_app, error_message => "Forbidden"
        )
    );

    my $guard = LWP::Protocol::PSGI->register($app);
    my $ua = LWP::UserAgent->new( cookie_jar => {} );

    my $token = $ua->get($url)->content;

    subtest "returns the same token for the same client" => sub {
        my $res = $ua->get($url);
        is $res->code, 200;
        is $res->content, $token;
    };

    subtest "returns different token for another client" => sub {
       my $another_token = LWP::UserAgent->new->get($url)->content;
       isnt $another_token, $token;
    };

    subtest "POST with valid token" => sub {
        my $res = $ua->post($url, [csrf_token => $token]);
        is $res->code, 200;
        is $res->content, $token;
    };

    subtest "POST with invalid token" => sub {
        my $res = $ua->post($url, [csrf_token => 'invalid token']);
        is $res->code, 403;
        is $res->content, 'Forbidden';
    };

    subtest "POST without token" => sub {
        my $res = LWP::UserAgent->new->post($url);
        is $res->code, 403;
        is $res->content, 'Forbidden';
    };

};

done_testing;