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 インスタンスの viewcontext を push しているにもかかわらず NoMethodError が出る。 Decorator のメソッドはみつかっているものの、その中の urlfor なパスがないって言われる。
調べてみた
ググッてみると、以下の記事をみつけた。
Testing Draper decorators with real helpers, including URL helpers.
hogepath、fugaurl のような Decorator内で urlfor なメソッドを使いたい場合は、viewcontext のレシーバとなる Controller のインスタンスに request を設定しなければならない様子。
これは ActiveDecoratorが、ここで methodmissing を利用して viewcontext にある同メソッドを呼び出すと思うのですが、ここで 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’ に対してホスト名とポートをくっつけたものを渡せば反映されるっぽいので、反映したらうまくポートも反映されたのであった。