なんか書いていこうぜー.com

Let's write something good

active_decorator の spec でハマった話

2015-07-30
  • Share on Facebook
  • Tweet
  • Add to Pocket

今日、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_optionsurl_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’ に対してホスト名とポートをくっつけたものを渡せば反映されるっぽいので、反映したらうまくポートも反映されたのであった。