SinatraでModularスタイルのときのテストでハマってた話

ざっくりまとめると

SinatraModulerスタイルで書いたときは、RSpecなどでテストするときに上書きするRack::Testのappメソッドを

def app
  Sinatra::Application
end

ではなく

def app
  HelloSinatra
end

みたいに、アプリケーションのクラス(上記の場合はHelloSinatra)を返すようにしないとテストがうまく動かないという話です。

環境

なにごと?

Testing Sinatra with Rack::Testを見つつこんな感じのコードとテストを書いてたわけですよ。

# app.rb
require 'sinatra/base'

class HelloSinatra < Sinatra::Base
  get '/' do
    'Hello, Sinatra!'
  end
end
# spec/requests/hello_spec.rb
ENV['RACK_ENV'] = 'test'

require File.join(File.dirname(__FILE__), '..', '..', 'app.rb')
require 'sinatra'
require 'rack/test'
require 'rspec'

describe 'HelloSinatra' do
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  it 'should be OK' do
    get '/'
    expect(last_response).to be_ok
  end
end

で、テストすると失敗するわけです。

$ rspec
F

Failures:

  1) HelloSinatra should be OK
     Failure/Error: expect(last_response).to be_ok
       expected ok? to return true, got false
     # ./spec/requests/hello_spec.rb:18:in `block (2 levels) in <top (required)>'

Finished in 0.03914 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/requests/hello_spec.rb:16 # HelloSinatra should be OK

なんでかなーと思ってModular vs. Classic Style - Sinatra: READMEを見つつ考えてみると、Testing Sinatra with Rack::TestSinatra+RSpecのサンプルはClassicスタイルで

require 'sinatra'
get '/' do
  'Hello world!'
end

って感じで書いてあります。 で、Classicスタイルだとconfig.ruは

require './app'
run Sinatra::Application

のようにSinatra::Applicationを起動します。 これはきっとClassicスタイルの場合はルータを定義したりするとSinatra::Applicationが拡張されていくってことかな(TODO: ちゃんと調べる)。

それに対してModulerスタイルで

require 'sinatra/base'
class HelloSinatra < Sinatra::Base
  get '/' do
    'Hello, Sinatra!'
  end
end

ってやるときは、config.ruは

require './app'
run HelloSinatra

のように、アプリケーションのクラスから起動します。

…ということはModulerスタイルの場合、RSpecでもappメソッドはSinatra::ApplicationではなくHelloSinatraを返さなきゃダメなんじゃないだろうか、ということで

# spec/requests/hello_spec.rb
ENV['RACK_ENV'] = 'test'

require File.join(File.dirname(__FILE__), '..', '..', 'app.rb')
require 'sinatra'
require 'rack/test'
require 'rspec'

describe 'HelloSinatra' do
  include Rack::Test::Methods

  def app
    HelloSinatra # ←Sinatra::Applicationから変更
  end

  it 'should be OK' do
    get '/'
    expect(last_response).to be_ok
  end
end

のように書きなおしてみると…

$ rspec
.

Finished in 0.03378 seconds
1 example, 0 failures

うまくいった!

まとめ

というわけで、Sinatraの動きをもう少しちゃんと調べましょうというお話でした。