ンンンパ

食べ物では丸焼きに興味があります

Ruby 外から Web アクセスする何か (Capybara とか、cli とか) を RSpec でテストするときのアクセス先をモックする

mmmpa.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmpa.net

のテストをこの方法で行いました。

本編

たとえば、Capybara は各種ブラウザを介するアクセスのため、webmock が効かず、別の gem が必要なのは有名です。 https://github.com/oesmith/puffing-billy

Capybara テストする際はこれでいいのですが、Capybara テストする場合には不便かもしれませんね。

また、バッククォートや Open3 を用いて呼びだすような cli、たとえば siege などのターゲットをモックすることはできません。cli におんぶにだっこの gem を開発するときのテストに、かなり不便ですね。

RSpec 開始時にサーバーをたてる

望んだ結果を返すアクセス先があればいいので、テスト中に起動する仮想サーバーをたてます。

public ディレクトリに適当な HTML を用意した上で、

RSpec.configure do |config|
  require 'webrick'
  config.before(:suite) do
    port = ENV['MOCK_PORT'] || 3000
    host = ENV['MOCK_HOST'] || '127.0.0.1'
    started = false

    Thread.start do
      WEBrick::HTTPServer.new(
        DocumentRoot: File.expand_path('./public/', __dir__),
        BindAddress: host,
        Port: port,
        AccessLog: [],
        StartCallback: ->{ started = true }
        Logger: WEBrick::Log::new("/dev/null", 7)
      ).tap { |server|
        Signal.trap(:INT) { server.shutdown }
        server.start
      }
    end

    while !started
      sleep 0.5
    end
  end
end

Thread はあたらしいものを用意しないと、sever.start の時点で RSpec のプロセスが停滞してしまいますので注意しましょう。

また、別 Thread になるので server.start が完了するしないにかかわらずテストに突入、テストが落ちるということがあるので、StartCallback を使って、きちんとサーバーがスタートしたことを確認してからはじめましょう。

単純なアクセス以外

さらに、リダイレクトなど、単純な HTML アクセス以外の処理が必要ならば、インスタンス servermount すると処理を追加できます。

# たとえばリダイレクト
# Rails を模するなら、301(Moved) ではなく 302(Found)
server.mount_proc('/redirect') do |req, res|
  res.set_redirect(WEBrick::HTTPStatus::Found, '/redirected.html')
end

できあがり

これで、以下のようなテストが、自由に行えるようになりました。

expect(`siege -t 10s http://127.0.0.1:3000/foo.html`).to be_truthy

例は siege 自体のテストのようになってしまっていますが、実際は取得した結果をあれこれしたものをテストしました。

雑ではありますが、とても楽に、多くのアクセス先を用意できるようになりました。