前提となるテストの内容
Railsチュートリアル本文、リスト 14.40記載のテストコードです。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:rhakurei)
@other = users(:mkirisame)
log_in_as(@user)
end
# ...略
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, params: { followed_id: @other.id }
end
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
end
test "should unfollow a user the standard way" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship)
end
end
test "should unfollow a user with Ajax" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship), xhr: true
end
end
end
RelationshipsController#create
において、format.js
以下の行が欠落していても上記テストが落ちない理由
前提として、RelationshipsController#create
の実装は以下のようになっています。
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
create
メソッド内2行目の時点で、follow
メソッドの処理に問題がなければ、@user.following.count
の数は1増えます。
ここで、format.js
以下の行が欠落している場合、RelationshipsController#create
は、「Ajaxによる呼び出しであっても、非Ajax(通常のリダイレクトを伴う呼び出し)として以降の処理を行います。となると、「format.js
以下の行が欠落していても、format.html
以下の行さえあれば、とりあえず当該テストは通ってしまう」という動作になるのです。
現状の実装で、テスト「should follow a user with Ajax」はどんな場合に落ちるのか
Railsチュートリアル本文のリスト 14.40において、テスト「should follow a user with Ajax」のソースコードは、以下のようになっています。
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
end
「RelationshipsController#create
において、format.js
以下の行が欠落していてもこのテストが落ちない」というのであれば、どのような場合にこのテストは落ちるのでしょうか。
一つの答えは、「app/views/relationships
ディレクトリに、create.js.erb
がなくてcreate.css.erb
だけが存在する場合」です。この場合、当該テストは以下のようなエラーで落ちることになります。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 507
Started with run options --seed 49836
ERROR["test_should_follow_a_user_with_Ajax", FollowingTest, 4.884168800000225]
test_should_follow_a_user_with_Ajax#FollowingTest (4.88s)
ActionController::UnknownFormat: ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant.
request.formats: ["text/javascript", "text/html", "application/xml", "*/*"]
request.variant: []
test/integration/following_test.rb:37:in `block (2 levels) in <class:FollowingTest>'
test/integration/following_test.rb:36:in `block in <class:FollowingTest>'
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.96005s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips
ActionController::UnknownFormat
エラーでテストが落ちていますね。
test/integration/following_test.rb
の、より望ましいであろう実装
ビューによりレンダリングされる内容に対してテストを行うのであれば、以下のような実装がより望ましいかと思います。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:rhakurei)
@other = users(:mkirisame)
log_in_as(@user)
end
...略
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, params: { followed_id: @other.id }
end
+ assert_redirected_to @other
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
+ assert_match 'Unfollow', @response.body
end
test "should unfollow a user the standard way" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship)
end
+ assert_redirected_to @other
end
test "should unfollow a user with Ajax" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship), xhr: true
end
+ assert_match 'Follow', @response.body
end
end
上記テストの追加内容は以下の通りです。
- 非Ajaxの場合、
@other
の内容たるユーザーのプロフィールページにリダイレクトされることをテストする - Ajaxの場合、以下の処理が行われることをテストする
- Followボタンが押された場合、「Unfollow」というキャプションを持つボタンを生成するJavaScriptコードが返されること
- Unfollowボタンが押された場合、「Follow」というキャプションを持つボタンを生成するJavaScriptコードが返されること
「より望ましいであろう実装」のテスト結果
「should follow a user the standard way」のテストが落ちるパターン
以下のように、「RelationshipsController#create
において、format.html
以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
- format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
実際にテスト「should follow a user the standard way」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 664
Started with run options --seed 25360
ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 4.109552399999302]
test_should_follow_a_user_the_standard_way#FollowingTest (4.11s)
ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat
app/controllers/relationships_controller.rb:7:in `create'
test/integration/following_test.rb:30:in `block (2 levels) in <class:FollowingTest>'
test/integration/following_test.rb:29:in `block in <class:FollowingTest>'
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.22645s
6 tests, 19 assertions, 0 failures, 1 errors, 0 skips
「should follow a user with Ajax」のテストが落ちるパターン
以下のように、「RelationshipsController#create
において、format.js
以下の行が欠落している」という場合、「should follow a user with Ajax」のテストが落ちることが期待されます。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
- format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
実際にテスト「should follow a user with Ajax」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 677
Started with run options --seed 49139
FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.8547959999996237]
test_should_follow_a_user_with_Ajax#FollowingTest (1.85s)
Expected /Unfollow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})".
test/integration/following_test.rb:39:in `block in <class:FollowingTest>'
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.41998s
6 tests, 22 assertions, 1 failures, 0 errors, 0 skips
「should unfollow a user the standard way」のテストが落ちるパターン
以下のように、「RelationshipsController#destroy
において、format.html
以下の行が欠落している場合」、「should follow a user the standard way」のテストが落ちることが期待されます。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
- format.html { redirect_to @user }
format.js
end
end
end
実際にテスト「should unfollow a user the standard way」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 690
Started with run options --seed 23512
ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 3.8890324999993027]
test_should_unfollow_a_user_the_standard_way#FollowingTest (3.89s)
ActionController::UnknownFormat: ActionController::UnknownFormat: ActionController::UnknownFormat
app/controllers/relationships_controller.rb:16:in `destroy'
test/integration/following_test.rb:46:in `block (2 levels) in <class:FollowingTest>'
test/integration/following_test.rb:45:in `block in <class:FollowingTest>'
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.18608s
6 tests, 19 assertions, 0 failures, 1 errors, 0 skips
「should unfollow a user with Ajax」のテストが落ちるパターン
以下のように、「RelationshipsController#destroy
において、format.js
以下の行が欠落している場合」、「should unfollow a user with Ajax」のテストが落ちることが期待されます。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
- format.js
end
end
end
実際にテスト「should unfollow a user with Ajax」を実行すると、結果は以下のようになります。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 703
Started with run options --seed 27626
FAIL["test_should_unfollow_a_user_with_Ajax", FollowingTest, 1.7373090000000957]
test_should_unfollow_a_user_with_Ajax#FollowingTest (1.74s)
Expected /Follow/ to match "Turbolinks.clearCache()\nTurbolinks.visit(\"http://www.example.com/users/391532587\", {\"action\":\"replace\"})".
test/integration/following_test.rb:57:in `block in <class:FollowingTest>'
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.29870s
6 tests, 22 assertions, 1 failures, 0 errors, 0 skips
ソースコードの実装に問題がない場合
最後に、app/controllers/relationships_controller.rb
の内容が以下の通り問題なく記述されている場合、テストの結果はどうなるでしょうか。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
実際にテストを実行してみます。
# rails test test/integration/following_test.rb
Running via Spring preloader in process 716
Started with run options --seed 34373
6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.64541s
6 tests, 22 assertions, 0 failures, 0 errors, 0 skips
無事テストが通りました。というわけで、テストの内容に問題はないようです。