active_decorator の spec でハマった話
今日、app/modelsに配置されているただの class で表示用のメソッドがごにょごにょ生えているのを ActiveDecorator に移動させてはどうかと指摘をいただいたので、移動させた。 spec が 1 件だけあったので、これも Decorator のテストに書き換えようとして、ハマった。
まとめ(先に)
- Thank you, @flada_auxv san!
- ActiveDecorator のテストをするとき、Controllerインスタンスから view_context を生成すると思うけど、ApplicationControlle#request を設定しておくと、URL使うテストでハマらない。
コード例と現象
まずは、例とするコード(若干適当なところが…)
class GroupSummary
def initialize(group, total_count, failure_count)
@group = group
@total_count = total_count.to_i
@failure_count = failure_count.to_i
end
end
module GroupSummaryDecorator
# NOTE これがポイント
def summary_url
group_summary_url(@group)
end
# こいつらはテストしても期待通りに動く
def success_count
@total_count - @failure_count
end
end
describe GroupSummaryDecorator do
let(:group) { FactoryGirl.create(:group) }
let(:group_summary) { GroupSummary.new(group, 0, 0) }
before do
ActiveDecorator::ViewContext.push ApplicationController.new.view_context
end
after do
ActiveDecorator::ViewContext.pop
end
describe '#summary_url' do
before do
ActiveDecorator::Decorator.instance.decorate group_summary
end
specify do
expect(group_summary.summary_url).to eq 'http://localhost:3000/groups/1000'
end
end
end
bundle exec rspec spec/decorators/group_summary_decorator_spec.rb
# => NoMethodError: undefined method `group_summary_url` for #<GroupSummary:0x00000000>
ActiveDecorator::ViewContext に ApplicationController インスタンスの view_context を push しているにもかかわらず NoMethodError が出る。 Decorator のメソッドはみつかっているものの、その中の url_for なパスがないって言われる。
調べてみた
ググッてみると、以下の記事をみつけた。
Testing Draper decorators with real helpers, including URL helpers.
hoge_path、fuga_url のような Decorator内で url_for なメソッドを使いたい場合は、view_context のレシーバとなる Controller のインスタンスに request を設定しなければならない様子。
これは ActiveDecoratorが、ここで method_missing を利用して view_context にある同メソッドを呼び出すと思うのですが、ここで actionpack/lib/action_controller/metal/url_for.rb
で上書きされている url_options
を url_for
で利用しており、その影響で request
が空だとうまく URL が作成できないため(らしい)。
ActionDispatch::TestRequest
という便利なものが用意されているので、これを ApplicationController#request
に代入してやるとうまいこと URL が作られた。
describe GroupSummaryDecorator do
let(:group) { FactoryGirl.create(:group) }
let(:group_summary) { GroupSummary.new(group, 0, 0) }
before do
controller = ApplicationController.new
controller.request = ActionDispatch::TestRequest.new
ActiveDecorator::ViewContext.push controller.view_context
end
--- 略 ---
end
bundle exec rspec spec/decorators/group_summary_decorator_spec.rb
# => passed
※ URL の port がうまく設定されず困っていたが、このTestRequestのリファレンスみると、‘HTTP_HOST’ に対してホスト名とポートをくっつけたものを渡せば反映されるっぽいので、反映したらうまくポートも反映されたのであった。