テストをするときに外部のAPIを叩く部分のテストを書きたい場合、出来る限り外部にアクセスせずにテストを書きたい。そういう時
- Test::Mock::Guardを使って該当部分のメソッドが特定のデータを返すように書き換えてしまう
- LWP::Protocol::PSGIを使うなど、APIを叩くclient部分を差し替える
- Test::TCPを使って、APIのserver部分を差し替える
などの方法がある。
今回はLWP::Protocol::PSGIを使ってAPIを叩くclient部分を差し替えるというのをやってみた。
テストしたい実装
例えばはてなブックマーク件数取得APIを使った実装があるとする。
以下の様な感じ。
package Bookmark::Client; use strict; use warnings; use URI; use URI::QueryParam; use LWP::UserAgent; use JSON::XS; sub get_bookmark_count { my ($class, $urls) = @_; my $ua = LWP::UserAgent->new; my $url = URI->new('http://api.b.st-hatena.com/entry.counts'); $url->query_param(url => $urls); my $res = $ua->get($url); my $json = decode_json($res->content); return $json; } 1;
このget_bookmark_countをテストしたい。
plack appを使ってLWP::UserAgentを差し替え
こういう時LWP::UserAgentを使っている場合はLWP::Protocol::PSGIを使って差し替えられる。テスト書くとしたらこういう感じ。
use Test::More; use Test::Deep; use LWP::UserAgent; use LWP::Protocol::PSGI; use Plack::Request; use JSON::XS; # 差し替える用appを作る, 単に渡されたURLにrandomな数字を返すだけ my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my @urls = $req->parameters->get_all('url'); my %res = map { $_ => int rand(1000) } @urls; return [200, ['Content-Type' => 'application/json'], [encode_json(\%res)]]; }; # api.b.st-hatena.comのアクセスだけこのappに向ける my $guard = LWP::Protocol::PSGI->register($app, host => 'api.b.st-hatena.com'); # 実行してみて構造が正しいかチェック my $data = Bookmark::Client->get_bookmark_count([ 'http://www.hatena.ne.jp/', 'http://b.hatena.ne.jp/', ]); cmp_deeply $data, { 'http://www.hatena.ne.jp/' => ignore(), 'http://b.hatena.ne.jp/' => ignore(), }, '返す構造が正しい'; done_testing();
もちろん今回のやつは結構簡単なのでテスト書かなくてもいいかもしれないけど、外部からいろんなresponseが来た時のテストを書きたい時はこういう風に書くと便利。
あとLWP::Protocol::PSGI使わなくても、Test::Mock::LWP::Conditionalみたいなのでやっても良さそう。
まとめ
とりあえず今回はクライアントのリクエスト部分の差し替えみたいなやつをやった。次はサーバ部分を立てるというのをまとめたい。