puppeteer-rubyを開発していて、いよいよ単体試験を書くことにした。
そこで問題となったのが、「ボタンだけを含むHTMLページを開く」みたいな試験。
本家Puppeteerでは静的なHTMLページをいくつか用意してHTTPサーバーを立ててmocha(JavaScriptのテスト)を流しているっぽいのだけど、単体試験としてそれはどうなの?
describe 'click button' do http_server do get '/button.html' do <<~HTML <html> <head><title>button</title></head> <body> <button onclick='document.body.innerHTML="123"'>click here</button>" </body> </html> end end it 'can click button' do page.goto('/button.html') page.click('button') expect(page.body).to eq("123") end end
こんな感じで必要最低限のHTMLを返すHTTPサーバーを都度立てるspecを書けたらいいのに?と思った。
Webmockはtoo much
RSpecでモックHTTPサーバーを作る方法をぐぐると、webmock が出てくる。
ただ、こいつは
このへんのソースを見ると、HTTPリクエストがされたかどうかを確認するマッチャなども定義されているし、今回はそもそもHTTP通信をモック化したいわけではなくて、テストサーバーを立てたいだけなのだから、やりたいことに対してあまりにもtoo muchだ。
SinatraのDSL
やりたいことの
http_server do get '/button.html' do <<~HTML <html> <head><title>button</title></head> <body> <button onclick='document.body.innerHTML="123"'>click here</button>" </body> </html> end
Sinatra::Baseのソースコードを読んでみたところ、
app = Sinatra.new do get '/hello' do 'Hello World' end end app.run! ... app.quit!
こんな感じで、HTTPサーバーが非常に簡単に立てることができる。
Sinatraを立てるDSLを定義する
さて、RSpecで冒頭に書いたようなspecを書けるようにするには、どうすればいいだろう?
とりあえずhttp_serverっていうメソッドをExampleGroupに生やしてやればいいのではないか。
module TestHttpServer def http_server(port: 8000, &block) require 'net/http' require 'sinatra/base' app = Sinatra.new(&block) around do |example| Thread.new { app.run!(port: port) } # wait until server is started. loop do Net::HTTP.get(URI("http://127.0.0.1:#{port}/")) break rescue Errno::ECONNREFUSED sleep 0.1 end example.run app.quit! end end end RSpec::Core::ExampleGroup.extend(TestHttpServer)
spec_helperを上記のようにしてみたら、うまくいった。
だいぶ無茶したけど、RSpecでテストサーバー建てるのをこんなかんじで書けるようにしてみた。https://t.co/NnAPmtI3SC pic.twitter.com/YRhR0AcWEl
— Yusuke Iwaki (@yi01imagination) 2020年7月19日
これで puppeteer-ruby がテスト駆動開発できるようになったぞ〜〜