LoginSignup
0
0

More than 3 years have passed since last update.

RSpecでテストHTTPサーバを立てて必要最小限のコンテンツで試験する方法

Posted at

Rubyでブラウザの自動操作ライブラリの試験を書いていたときに、

Slice.png

こんな感じで必要最低限のHTMLコンテンツのページで試験がしたくなった。

Webmockを使う?

RSpecでHTTPテストサーバーを、みたいなのでぐぐると、なぜかよく出てくる。
https://github.com/bblimke/webmock

ただ、今回は最小コンテンツでHTTPサーバを立てたいのであって、モックしたいわけじゃないので、却下。

SinatraのDSLをRSpecで書けたら良さそうじゃない?

これは完全に自分の好みではあるが、

describe 'click button' do
  sinatra do
    get '/button.html' do
      <<~HTML
      <html>
        <head><title>button</title></head>
        <body>
          <button onclick='document.getElementById("mytext").innerHTML="Clicked!"'>
            Click here
          </button>
          <p id="mytext">Not Clicked Yet</p>
        </body>
      </html>
      HTML
    end
  end

  it 'can click button' do
    page.goto('/button.html')
    page.click('button')
    expect(page.Seval('p', 'el => el.textContent')).to eq("Clicked")
  end
end

こんな感じで、やりたいことをどストレートに書けたらいいなーと思った。

SinatraでHTTPサーバーを開始・終了するには?

Sinatraのサンプルコードだと、グローバルにHTTPサーバー定義をしているものが多いが、リファレンスやソースをよく読むと

app = Sinatra.new do
  get '/hello' do
    'Hello World'
  end
end

こんな感じでクラスベースでサーバーを定義することができるし、 run!quit! で開始・終了できることがわかる。

app.run! # 開始 (ブロッキングされるので別スレッド必須)

app.quit! # 終了

参考:https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb

sinatra do ... end を実装してみた

RSpec::Core::ExampleGroupに特異メソッドとして sinatra を定義すればいいので、大枠は以下のように。

module SinatraTestHttpDSL
  def sinatra(&block)
    ...
  end
end

RSpec::Core::ExampleGroup.extend(SinatraTestHttpDSL)

んで、sinatraメソッドの実装としては around フックを仕込んで example.runの前後で app.run!, app.quit! すればいい。

def sinatra(port: 4567, &block)
  # HTTPサーバーを定義
  app = Sinatra.new(&block)

  around do |example|
    # HTTPサーバーを起動
    Thread.new { app.run!(port: port) }

    # HTTPサーバーが疎通可能になるまで待つ。(雑w
    loop do
      Net::HTTP.get(URI("http://127.0.0.1:#{port}/"))
      break
    rescue Errno::ECONNREFUSED
      sleep 0.1
    end

    # テスト実行
    example.run

    # HTTPサーバーを終了
    app.quit!
  end
end

まとめ

てきとうに↓のように定義すれば

spec/spec_helper.rb
require 'bundler/setup'

# ここから ↓↓↓
module SinatraTestHttpDSL
  def sinatra(port: 4567, &block)
    require 'net/http'
    require 'sinatra/base'
    require 'timeout'

    # HTTPサーバーを定義
    app = Sinatra.new(&block)

    around do |example|
      # HTTPサーバーを起動
      Thread.new { app.run!(port: port) }

      # HTTPサーバーが疎通可能になるまで待つ
      Timeout.timeout(3) do
        loop do
          Net::HTTP.get(URI("http://127.0.0.1:#{port}/"))
          break
        rescue Errno::ECONNREFUSED
          sleep 0.1
        end
      end

      # テスト実行
      example.run

      # HTTPサーバーを終了
      app.quit!
    end
  end
end

RSpec::Core::ExampleGroup.extend(SinatraTestHttpDSL)
# ここまで ↑↑↑

RSpec.configure do |config|
  # Enable flags like --only-failures and --next-failure
  config.example_status_persistence_file_path = '.rspec_status'

 .... (後略) 

↓こんなかんじで必要最小限のコンテンツのHTTPサーバーを立てた状態でRSpec動かすことができる。

describe 'click button' do
  sinatra do
    get '/button.html' do
      <<~HTML
      <html>
        <head><title>button</title></head>
        <body>
          <button onclick='document.getElementById("mytext").innerHTML="Clicked!"'>
            Click here
          </button>
          <p id="mytext">Not Clicked Yet</p>
        </body>
      </html>
      HTML
    end
  end

  it 'can click button' do
    page.goto('/button.html')
    page.click('button')
    expect(page.Seval('p', 'el => el.textContent')).to eq("Clicked")
  end
end

実際にやったコード

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0