Time to Read
3分
mocksmtpd という便利ルビージェムがある。詳細な使い方は 作者さんの日記 にもあるが、今日はより簡単にメール配信のテストが出来るようなおまじないのやり方を紹介する(主に、未来の自分が見返すために)。
インストール
rubygems.org には謎のフォークされた gem がホストされていて胡散臭い。さいわい、githubにホストされているため、今日びのフレームワークなら bundler とかを使って楽にインストールできる。
Gemfile:
1 2 3 | group :test do gem 'mocksmtpd', :git => "git://github.com/koseki/mocksmtpd.git" end |
しかるのち bundle コマンド。
準備
動作に必要な設定ファイルやディレクトリ~がある。APP_ROOTにもぐって以下を実行。
1 2 3 4 5 | $ bundle exec mocksmtpd init Created: mocksmtpd/ Created: mocksmtpd/inbox/ Created: mocksmtpd/log/ Created: mocksmtpd/mocksmtpd.conf |
mocksmtpd/mocksmtpd.conf はほぼデフォルトのままで良いが、 UN*X 系のシステムでは 1024 番以下のポートをリスンするには root 権限が必要で、テストのために sudo するわけにもいかないので、ポートだけは変えておく。 10025番とかいいんじゃないでしょうか:
1 2 3 4 5 6 7 8 9 10 | ServerName: mocksmtpd Port: 10025 RequestTimeout: 120 LineLengthLimit: 1024 LogLevel: INFO LogFile: ./log/mocksmtpd.log PidFile: ./log/mocksmtpd.pid InboxDir: ./inbox Umask: 2 |
ここまできたら、
1 | $ bundle exec mocksmtpd -f mocksmtpd/mocksmtpd.conf |
とすることで、モックの smtp サーバが立ち上がるはずなので、 lsof -i:10025 したり、メールを送りつけたりできるか試してほしい。
で、以下は RSpec 利用の場合なので適宜読み替えてほしい。
RSpec の before フックで、 mocksmtpd サーバを立ち上げるスレッドを用意する。こんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | before(:all) do # サンプルは Padrino なので、こういう便利APIがある。普通に Sinatra とかの場合は適宜読み替え app_root = Padrino.root # 下処理 FileUtils.rm Dir.glob(File.join(app_root, "mocksmtpd", "inbox", "*")) @conf = File.join(app_root, "mocksmtpd", "mocksmtpd.conf") @smtp_thread = Thread.new do Mocksmtpd.new(["-f", @conf]).run end # 10025 番ポ~トがリスンされるまで待つ until (TCPSocket.new("localhost", 10025) rescue false) end end |
after フックの後始末は今回やらない。理由は:
- テストプロセスが終了すれば無事 SMTP サーバも終了する
- SMTPD プロセスを一度立ち上げれば、テスト終了までプロセスを落とす理由がない
- そもそも Mocksmtpd 自身にポートを unlisten する API が用意されていないため、上手にできない(Thread を kill しても unlisten されません)
送ったメールを取得する
たとえばパドリーノフレームワークであれば、以下のような設定を追加することで、テスト環境でのメール送信をアレできる。
1 2 3 4 5 6 7 8 9 10 | class DoMailApp < Padrino::Application configure :test do set :delivery_method, :smtp => { :address => "localhost", :port => 10025, :enable_starttls_auto => true, :openssl_verify_mode => OpenSSL::SSL::VERIFY_NONE } end end |
Rails とかでも同じような感じだし、まあ昨今は Pony とかも使うと思うけれど大体一緒(どれも Mail gem 使ってますんで)。
で、ここで一点困ったことが、 mocksmtpd はそもそも 送ったメールをHTMLにして目視確認するために 作られたものなので、プログラムに読ませるには若干工夫が必要になる。
以下をご参照あれ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | include Rack::Test::Methods def app DoMailApp end describe DoMailApp do it "sends email" do # メールを送るエンドポイント get "/send/email" # メールは、 APP_ROOT/mocksmtpd/inbox/(\d+ = 送信日時).html に保存される filename = Dir.glob(File.join(Padrino.root, "mocksmtpd", "inbox", "*.html")). select{|n| n =~ /\d+\.html/}.first # メールからメール本体部分だけを抜き取る # nokogiri を使えれば便利だが、まあ、以下の正規表現でも大丈夫 re = /<div id="source" style="border: solid 1px #666; background:white; padding:2em;">\n(.*)<\/p>\n<\/div>/m body = open(filename, "r").read.scan(re).flatten.first.gsub(/<br.*>/, '') body.should match /Subject:\s*A Mail Sent/ end end |
おもったこと
そもそも自動テスト向けの作りになっていないこともあって、今の API 具合ではやっぱり自動化テスト時に不便な感じがするので、メールソースそのまま保存する API とか、あと停止する API とかを追加したものを fork すると便利に使えそう。
あと、他のプロトコル( irc とか)でも同じ考え方で応用できそう。




