Feature Specで複数ユーザーのやりとり(マルチセッション)をテストする

はじめに

インターンのhmryuです。今週、Misocaのインターンを卒業しました。僕は、昨年のお盆明けから約7ヶ月間Misocaでエンジニアとして働いていました。とくに後半の4ヶ月は、発注機能の開発に関わっていました。

現在の発注機能では、複数のユーザーがコメントのやりとりやファイル共有を経て、発注できるしくみになっています。

もともと、発注機能のFeature Specでは、コメントや発注などを別々のsenarioで書いていましたが、それだと不必要に冗長な部分が増えてしまいコードの修正が難しくなっていました。そこで、1つのscenarioで発注機能全体をテストできるように大幅な修正を行いました。

(Controller Specについては、ミニマムリリースを意識していたらコードが肥大化していた話にまとめてあります。)

f:id:hmryu:20160323131237p:plain

マルチセッションのFeature Spec

そこで問題になったのは、セッションの切り替えを行う必要があるという点です。例えば「あるユーザーがコメントした後に、その通知メールを受け取った別のユーザーがリンクを開いてコメントする」という動作では、ユーザー間でセッションが引き継がれてしまっていると、うまくテストができません。

そこで、Using multiple Capybara sessions in RSpec request specsを参考にして以下のようなメソッドを定義しました。

def in_browser(name)
  old_session = Capybara.session_name
  Capybara.session_name = name
  yield
  Capybara.session_name = old_session
end

in_browser メソッドに、ユーザーごとの処理をブロックとして渡すことで、セッションを切り替えながらテストすることができます。実際のFeature Specは以下のようになりました。

scenario 'コメントでやりとりをし、発注者が発注できる' do
  # 発注者がファイルをアップロードする
  in_browser(:buyer) do
    # ... logic ...
    expect_to_show_download_button
    expect_to_upload_file(file)
    # ... logic ...
  end

  # 受注者がファイルを受け取ってコメントする
  in_browser(:seller) do
    # ... logic ...
    click_document_url_in_mail

    expect_to_show_uploaded_file(file)
    expect_to_submit_comment('ファイル受け取りました')
    # ... logic ...
  end
end

補足

Capybara.current_session は以下のように実装されているため、Capybara.session_name を変えることで異なるセッションを参照するようにできます。

https://github.com/jnicklas/capybara/blob/master/lib/capybara.rb#L319

module Capybara
  class << self
    def current_session
      session_pool["#{current_driver}:#{session_name}:#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
    end

    def session_name
      @session_name ||= :default
    end
  end
end

まとめ

実際にユーザーが使う流れをそのままscenarioに書くことで、テストがわかりやすくなり、変更も加えやすくなりました。

7ヶ月と短い期間でしたが、Misocaのインターンでは、エンジニアとして様々な経験ができました。名古屋近辺の学生で、エンジニアのインターンに興味がある方はぜひMisocaにジョインしてください!

おまけ:重複した処理をメソッドに切り出す

各ユーザーに複数のメールが届くので、下記のようなメソッドを作っておくと便利でした。

def inbox(email)
  ActionMailer::Base.deliveries.select { |m| m.to.include?(email) }
end

実際に使うときは、下記のようになります。

in_browser(:buyer) do
  mail = inbox(email).last
  # ... logic ...
end