画像アップローダーのテスト(オリジナル)
Railsチュートリアル本文においては、第13章の演習 - 基本的な画像アップロードに登場するテストです。
同演習でFILL_IN
となっている箇所を埋めると、テストの内容は以下のようになります。
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:rhakurei)
@other_user = users(:skomeiji)
end
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'img.gravatar'
assert_select 'h1', @user.name
assert_select 'span', /#{@user.microposts.count}/
assert_select 'form[action="/microposts"]'
assert_select 'textarea'
assert_select 'div.pagination'
assert_select 'input[type="file"]'
# 無効な送信
assert_no_difference 'Micropost.count' do
post microposts_path, params: { micropost: { content: "" } }
end
assert_select 'div#error_explanation'
# 有効な送信
content = "This micropost really ties the room together"
picture = fixture_file_upload('test/fixtures/lgtm3.png', 'image/png')
assert_difference 'Micropost.count', 1 do
post microposts_path, params: { micropost: { content: content, picture: picture } }
end
assert assigns(:micropost).picture?
assert_redirected_to root_url
follow_redirect!
assert_match content, response.body
# 投稿を削除する
assert_select 'a', text: 'delete'
first_micropost = @user.microposts.paginate(page: 1).first
assert_difference 'Micropost.count', -1 do
delete micropost_path(first_micropost)
end
# 違うユーザーのプロフィールにアクセス(削除リンクがないことの確認)
get user_path(users(:mkirisame))
assert_select 'a', text: 'delete', count: 0
end
# ...略
end
このテストに関する一つの不具合
実は、上記のテストには一つ不具合があります。
例えば、以下のようにapp/views/microposts/_micropost.html.erb
から画像表示部分の実装を取り除くと、テストの結果はどうなるでしょうか。
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content">
<%= micropost.content %>
- <%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
以下のようにテストが成功してしまうのです。
# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15428
Started with run options --seed 30155
2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.32782s
2 tests, 23 assertions, 0 failures, 0 errors, 0 skips
「画像を投稿したのに、投稿した画像がフィードに表示されていない」というのは、テストで検出できるべきバグであり、それを検出できないのはテストの不具合です。早速、テストに存在する当該不具合を解消していきましょう。
画像アップローダーのテストに存在する不具合を解消する
test/integration/microposts_interface_test.rb
を以下のように変更するのが一つの方法です(他にも方法はあるかもしれません)。
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:rhakurei)
@other_user = users(:skomeiji)
end
test "micropost interface" do
log_in_as(@user)
get root_path
...略
# 有効な送信
content = "This micropost really ties the room together"
picture = fixture_file_upload('test/fixtures/lgtm3.png', 'image/png')
assert_difference 'Micropost.count', 1 do
post microposts_path, params: { micropost: { content: content, picture: picture } }
end
- assert assigns(:micropost).picture?
assert_redirected_to root_url
follow_redirect!
assert_match content, response.body
+ assert_select "img[src*='#{picture.original_filename}']"
# 投稿を削除する
...略
end
...略
end
新たなテストのポイント
ポイントは以下のassert_select
です。
assert_select "img[src*='#{picture.original_filename}']"
picture.original_filename
というのは、「アップロードしたファイルのローカルにおけるファイル名」と考えてかまいません1。また、CSSセレクタで部分一致検索を行うためには、*=
という演算子を用います。
結果、上記assert_select
は、「src
属性に、アップロードしたファイルのローカルにおけるファイル名を含むimg
要素が存在すること」をチェックします。
また、Rails5で非推奨とされているassigns
を使わない形となり、Webアプリケーションのテストの書き方としてより望ましいとされる「Request Specなテストの書き方」に近づけることができます。
実装を変更したテストが、本当に正しい対象へのテストなのか
マイクロポストに関連付けられた画像を表示する部分が欠落したビューに対してテストを実行する
app/views/microposts/_micropost.html.erb
の内容が以下のようになっている場合、テストの結果はどうなるでしょうか。
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content">
<%= micropost.content %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15608
Started with run options --seed 24115
FAIL["test_micropost_interface", MicropostsInterfaceTest, 4.238321199998609]
test_micropost_interface#MicropostsInterfaceTest (4.24s)
Expected at least 1 element matching "img[src*='lgtm3.png']", found 0..
Expected 0 to be >= 1.
test/integration/microposts_interface_test.rb:34:in `block in <class:MicropostsInterfaceTest>'
2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.51704s
2 tests, 20 assertions, 1 failures, 0 errors, 0 skips
Expected at least 1 element matching "img[src*='lgtm3.png']", found 0..
Expected 0 to be >= 1.
test/integration/microposts_interface_test.rb:34
以上のようなメッセージが出てテストが失敗しています。「lgtm3.png
という文字列を含むsrc
属性を持つimg
要素が、1つ以上なければならないのに、1つも存在しない」という趣旨のメッセージですね。
私の環境では、test/integration/microposts_interface_test.rb
の34行目は以下内容になっています。
assert_select "img[src*='#{picture.original_filename}']"
確かに想定通りの形でテストが失敗しているといえます。
マイクロポストに関連付けられた画像を表示する部分を正しく実装したビューに対してテストを実行する
app/views/microposts/_micropost.html.erb
の中身を、今度は以下のようにしてテストを実行してみましょう。
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
# rails test test/integration/microposts_interface_test.rb
Running via Spring preloader in process 15621
Started with run options --seed 58972
2/2: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.61052s
2 tests, 23 assertions, 0 failures, 0 errors, 0 skips
今度こそテストが無事成功しました。
関連リンク
「CSSにおける部分一致検索」については、以下の記事を参考にさせていただきました。ありがとうございます。
-
より厳密には、「CarrierWaveデフォルトの状態だと日本語の文字が"__"に置き換えられてしまう」等の制限があるようなのですが、本記事で扱う内容の範囲外となるため、そのあたりの説明は割愛します。 ↩