torutkのブログ

ソフトウェア・エンジニアのブログ

[Redmine]Redmine 4.0(Rails 5.2)のプラグインをテストするなど(コントローラーのテスト編の続き)

Redmine 4.0(Rails 5.2)のプラグインをテストするなど(コントローラーのテスト編) - torutkのブログ では、コントローラーのテストにRails 5で雛形生成されるActionDispatch::IntegrationTestを少し追求してみましたが頓挫してしまい、Rails 4までのActionController::TestCaseでのテストを進めることにします。

最初のテスト項目

最初に、indexアクションを呼び応答が成功を返すことをテスト項目とします。
まず、雛形として生成されたTermCategoriesControllerクラスのテストクラスは次です

require File.dirname(__FILE__) + '/../test_helper'

class TermCategoriesControllerTest < ActionController::TestCase
  # Replace this with your real tests.
  def test_truth
    assert true
  end
end
試みの1

1つだけ定義されるテストメソッドの名前を、test_index_responseとします。テストメソッドの中でindexアクションをgetで呼び出します。呼び出した結果が成功かどうかをassertで判定します。次のコードとなります。

  def test_index_response
    get :index
    assert_response :success
  end

実行結果はエラーとなりました。

Error:
TermCategoriesControllerTest#test_index_response:
ActionController::UrlGenerationError: No route matches {:action=>"index", :controller=>"term_categories"}
    plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:6:in `test_index_response'

term_categories は、プロジェクトにネストしているので、URLパスは /projects/:project_id/term_categories のようになりますが、ここではget呼び出し時にプロジェクトの指定をしていません。そのため、URLパスは /term_categories のようになり、ルーティング設定に合致しないのでエラーとなります。

試みの2

URLパスがプロジェクトのネストとなるように、プロジェクト識別子を追加します。試みの1との差分は次となります。

   def test_index_response
-    get :index
+    get :index, params: { project_id: 1 }
     assert_response :success
   end

実行結果は次のように故障(failure)となりました。

Failure:
TermCategoriesControllerTest#test_index_response [/work/redmine/plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:7]:
Expected response to be a <2XX: success>, but was a <404: Not Found>

ルーティング設定に合致し、処理が少し進みましたが、リソースが見つからないので404の応答となっています。プロジェクトID 1に対応するプロジェクトが存在していないので(多分)、このような結果となります。

Redmine本体のtest/fixtures/ディレクトリ下には、テストデータが用意されているので、プロジェクトのテストデータを利用することとします。

試みの3

プロジェクトのデータを、fixtures/projects.ymlから読み込むよう指定します。

 class TermCategoriesControllerTest < ActionController::TestCase
+  fixtures :projects

   def test_index_response

実行結果は次のように故障(failure)となりました。

Failure:
TermCategoriesControllerTest#test_index_response [/work/redmine/plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:9]:
Expected response to be a <2XX: success>, but was a <403: Forbidden>

今度はリソースへのアクセスが禁止(アクセス権がない)403の応答となっています。
指定したプロジェクトで用語集プラグインが有効化されていないためと推測します。

そこで、プロジェクトで用語集プラグインを有効化させるメソッドを先に呼びます。

試みの4

fixturesで用意されているプロジェクトデータの中から、今回はID=1のデータを読み込み、そのプロジェクトでモジュール(プラグイン)用語集を有効化します。fixuturesにあるデータを参照するには、ID等から検索するか、fixturesのテストデータを取得するメソッドを利用するかの方法があります。

fixtures/projects.yml のテストデータ(ID=1)は次のようになっています(抜粋)。

projects_001:
  created_on: 2006-07-19 19:13:59 +02:00
  name: eCookbook
  updated_on: 2006-07-19 22:53:01 +02:00
  id: 1
  :(略)

このテストデータを参照する方法は、モデルの検索で次のように取得するものと、

project1 = Project.find(1)

fixutres参照専用のメソッド

fixtures :projects
  :
  project1 = projects('projects_001')

と、fixturesで指定した名称(YAMLファイル名の拡張子を除いた名前)と同名で用意されるメソッドを使うものがあります。

今回は、後者の方法でfixturesにあるプロジェクトデータへの参照を取得します。

  def test_index_response
    projects('projects_001').enabled_module_names = [:glossary]
    get :index, params: { project_id: 1 }
    assert_response :success
  end

プロジェクトのモジュール(プラグイン)を有効化するメソッドenabled_module_namesで用語集プラグインのモジュール名glossaryを指定しています。

実行結果は次のように故障(failure)となりました。

Failure:
TermCategoriesControllerTest#test_index_response [/work/redmine/plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:9]:
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://test.host/login?back_url=http%3A%2F%2Ftest.host%2Fprojects%2F1%2Fterm_categories>
Response body: <html><body>You are being <a href="http://test.host/login?back_url=http%3A%2F%2Ftest.host%2Fprojects%2F1%2Fterm_categories">redirected</a>.</body></html>

用語集プラグインの権限の設定で参照するにはプロジェクトのメンバーでログインしている必要があるので、未ログインでアクセスするとログインページにリダイレクトされています。

そこで、テストの実行にあたりログインを再現しておきます。

試みの5

リクエストパラメーターにユーザーIDを指定します。

  fixtures :projects, :users

  def test_index_response
    projects('projects_001').enabled_module_names = [:glossary]
    @request.session[:user_id] = users('users_002').id
    get :index, params: { project_id: 1 }
    assert_response :success
  end

実行結果は次のように故障(failure)となりました。

Failure:
TermCategoriesControllerTest#test_index_response [/work/redmine/plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:10]:
Expected response to be a <2XX: success>, but was a <403: Forbidden>

ログイン済みでアクションを呼び出しているので、loginアクションへのリダイレクトが発生することはなくなりましたが、ログイン済みのユーザーのロールが用語集プラグインの権限を有していないので403応答となっています。

そこで、ユーザー(ID=2)のロールに、用語集プラグインの権限を付与します。

試みの6

テストデータのプロジェクト(ID=1)にユーザー(ID=2)が所属しているかをmembersテーブルで確認します。テストデータなのでfixtures/members.ymlを調べると、次のとおりproject_id=1にuser_id=2が所属していることが確認できました。

members_001:
  created_on: 2006-07-19 19:35:33 +02:00
  project_id: 1
  id: 1
  user_id: 2
  mail_notification: true

次に、ユーザー(ID=2)がどのロールでプロジェクト(ID=1)に属しているかを、member_rolesテーブルで確認します。テストデータではfixures/member_roles.ymlを調べると、次の通りmember_id=1はrole_id=1のロールで属していることが分かります。

member_roles_001:
  id: 1
  role_id: 1
  member_id: 1

roles_id=1は、fixtures/roles.ymlを調べると、

roles_001:
  name: Manager
  id: 1
  builtin: 0
  :(略)

となっています。

そこで、ロール(ID=1)に用語集プラグインの権限を付与するコートを追加します。

  fixtures :projects, :users, :roles

  def test_index_response
    projects('projects_001').enabled_module_names = [:glossary]
    roles('roles_001').add_permission! :manage_term_categories
    @request.session[:user_id] = users('users_002').id
    get :index, params: { project_id: 1 }
    assert_response :success
  end

実行結果は次のとおり故障(failure)となりました。

Failure:
TermCategoriesControllerTest#test_index_response [/work/redmine/plugins/redmine_glossary/test/functional/term_categories_controller_test.rb:11]:
Expected response to be a <2XX: success>, but was a <403: Forbidden>

ここでは成功を期待していましたが、どうしてでしょうか。

テストデータの参照で、プロジェクトからロールを辿るのに、先ほど、membersテーブル、member_rolesテーブルを順に手繰ってrolesテーブルに至ると記述しました。テストコードの実行において権限の確認で同様にプロジェクトからロールへ辿るために、fixturesの指定でこの手繰る際に参照する各テーブルのデータを指定しておく必要があるようです。

試みの7

ということでfixturesの指定を追加します。

  fixtures :projects, :users, :roles, :members, :member_roles

  def test_index_response
    projects('projects_001').enabled_module_names = [:glossary]
    roles('roles_001').add_permission! :manage_term_categories
    @request.session[:user_id] = users('users_002').id
    get :index, params: { project_id: 1 }
    assert_response :success
  end

実行結果は、次の通り成功となりました。

...

Finished in 0.554461s, 5.4107 runs/s, 5.4107 assertions/s.
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

setupメソッドに準備設定を記述

さて、最初のテスト項目が成功するようになりました。
次にテスト項目を追加していく際に、すべてのテストメソッドに準備設定としてプロジェクトのモジュール(プラグイン)有効化、ロールに権限の付与、を記述するのは冗長です。
そこで、setupメソッドに準備設定を記述します。

require File.dirname(__FILE__) + '/../test_helper'

class TermCategoriesControllerTest < ActionController::TestCase
  fixtures :projects, :users, :roles, :members, :member_roles

  def setup
    @project = projects('projects_001')
    @project.enabled_module_names = [:glossary]
    roles('roles_001').add_permission! :manage_term_categories
  end

  def test_index_response
    @request.session[:user_id] = users('users_002').id
    get :index, params: { project_id: 1 }
    assert_response :success
  end
end