Railsのフロントエンドのノウハウ#1: システムテスト編(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

記事を2本に分割しました。日本語タイトルは内容に即したものにしています。

Railsのフロントエンドのノウハウ#1: システムテスト編(翻訳)

記事の長さ: 8分

Rails 5以降の新しい機能のひとつに「システムテスト」があります。システムテストは、Capybaraを用いたフル装備のフロントエンドテスト機能を提供し、通常のユーザーが体験する完璧なWebエクスペリエンスを提供する本物のブラウザウィンドウを用いて各テストを実行します。この機能がRailsに組み込まれたことでテストがエレガントになり、テストに全力で集中できるようになります。

さらにうれしいことに、標準のRailsジェネレータを用いてモデル名や属性フィールドを生成すると、標準のCRUDシステムテストを自動生成してくれるので、フロントエンドフレームワークの差し替え作業が大幅にシームレスになります。

今回の記事では、前回の記事で用いたRails+VueJSアプリでコード変更が生じたときにシステムテストをすべてパスさせてご覧にいれます。その他に、テストで有用なJavaScriptの知識についてもいくつかご紹介いたします。

フロントエンドアプリを更新する

ここでは、前回の「VueJS Components with CoffeeScript for Rails」記事で使ったアプリを変更します。前回までのソースコードはdanielpclark/vue_exampleでご覧いただけます。

システムテストを実行するには、プロジェクトディレクトリで以下のコマンドを実行します。

rails test:system

訳注: 初めて実行する人はyarnをインストールしたうえでyarn add rails-erb-loaderを実行しておきましょう。場合によってはyarn install --ignore-enginesの実行も必要かもしれません。

リソースの作成と更新の部分でエラーが2件表示されるはずです。「Unable to find visible field “Body” that is not disabled」というエラーメッセージが表示されます。

上述のエラーはこのフィールドでのみ表示されます。これがこのテストで最初のエラーであるためです。テストの順序を変更すると、作成や更新にあるすべてのフィールドについて同様にテストが失敗します。これはテストするフィールドをCapybaraから探索する方法に関連しているはずです。

訳注: 手元のRuby 2.5.1/Rails 5.2.0環境では、最初だけ上述のエラーが1件表示されましたが、その後エラーは表示されなくなりました。springを止めてやり直しても、やはりエラーは表示されませんでした。

Railsのフォーム系メソッドでラベルや入力フィールドを生成すると、HTMLのlabel要素にforフィールドができます。ここには、対象となるあらゆる入力のidオブジェクトの名前が含まれます。私たちが独自のVueJSコンポーネントテンプレートを用いて新しいフォームを手作りしたときに、これらのフィールドについてはテストを書いていませんでした。そのため、app/javascript/form-document.vue.erbファイル内のフォーム用フィールドを変更する必要があります。

<label>Subject</label>
<input type="text" v-model="document.subject" />

<label>State</label>
<select v-model="document.state">
  <%= options_for_select(Document.states.keys, "concept") %>
</select>

<label>Body</label>
<textarea v-model="document.body"></textarea>

上を以下のように変更します。

<label for="document_subject">Subject</label>
<input id="document_subject" type="text" v-model="document.subject" />

<label for="document_state">State</label>
<select id="document_state" v-model="document.state">
  <%= options_for_select(Document.states.keys, "concept") %>
</select>

<label for="document_body">Body</label>
<textarea id="document_body" v-model="document.body"></textarea>

CapybaraにSubjectfill_inするよう指示を出すと、Capybaraはそのラベルのコンテンツとforの値を元に対象を取得し、そのforの値で使われているidを対象とします。この方法のよい点は、それに続く入力フィールドを対象として指定しなくても、このラベルを用いて同じように対象を指定できることです。

(修正後に)rails test:systemを再実行すると、同じエラーメッセージが表示されます。これはRails 5の設定でprotect_from_forgeryがテスト環境で使われていないことに関連するはずで、そのためCSRFトークンが生成されなくなっています。私たちのVueJSコードが失敗したのは、メタ属性フィールドが利用できることを明示的に要求しているためです。

これは以下のいずれかの方法で修正できます。

  • 自分のVueJSコードを編集して、CSRFトークンが存在しなくても動作するようにする(上述のアドバイス)。
  • config/environments/test.rbファイルを以下のように変更する。
# Forgery protection
config.action_controller.allow_forgery_protection = true

フロントエンド側のフォーム送信の実装によっては、ApplicationControllerに以下のコードがないと動かない可能性もあります。私たちの場合はこれは不要になります。

protect_from_forgery prepend: true

原注: 問題発生の原因を突き止めるには、RAILS_ENV=testを指定してrails serverを実行する必要があります。これにより、Documentリソースのneweditリソースを表示したときにブラウザのコンソールでJavaScriptのエラーが発生してる様子を確認できるようになります。

rails test:systemを実行すると、今度は「Update DocumentボタンやCreate Documentボタンが見つからない」というエラーメッセージに変わります。これはRailsが送信ボタンとして生成したと命名スキーマがフォームヘルパーで使われている可能性があるため、テストを更新して現在のボタン名を反映する必要があります。

test/system/documents_test.rbを開き、click_onの対象を"Create Document""Update Document"から"Submit"に変更します。これでテストを実行すると、今度は正しいflash通知が結果の中に見当たらないというメッセージが表示されますので、このflashメッセージを追加する必要があります。以下のapp/views/layouts/application.html.erbアプリケーションテンプレート内の<body>タグの内側に以下を追加します。

<% flash.each do |name, msg| -%>
  <%= content_tag :div, msg, class: name %>
<% end -%>

続いてコントローラのupdateアクションやnewアクションを更新して、適切なflash通知が表示されるようにします。app/controllers/documents_controller.rbを以下のように変更します。

def create
  @document = Document.new(document_params)

  respond_to do |format|
    if @document.save
      flash[:notice] = 'Document was successfully created.'
      format.html { redirect_to @document, notice: 'Document was successfully created.' }
      format.json { render :show, status: :created, location: @document }
    else
      format.html { render :new }
      format.json { render json: @document.errors, status: :unprocessable_entity }
    end
  end
end

def update
  respond_to do |format|
    if @document.update(document_params)
      flash[:notice] = 'Document was successfully updated.'
      format.html { redirect_to @document, notice: 'Document was successfully updated.' }
      format.json { render :show, status: :ok, location: @document }
    else
      format.html { render :edit }
      format.json { render json: @document.errors, status: :unprocessable_entity }
    end
  end
end

これでrails test:systemを実行すると、システムテストがすべてパスするようになります。


関連記事

Rails 5.1以降のシステムテストをRSpecで実行する(翻訳)

Rails 5: WebSocketのマルチセッションをMiniTestとシステムテストでテストする(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ