ActiveSupport_TestCase + shoulda によるテスト のバックアップ(No.1)

更新


公開メモ

ActionController::TestCase + shoulda による Ruby on Rails コントローラのテスト

  • ActionController::TestCase は Rails 標準のコントローラテストフレームワーク
  • shoulda は読みやすいテストケースを書くための gem

これらを使ってコントローラをテストするためのノウハウをメモとして残したい

いろいろ検討中

まだちゃんとしたテストは書き始めたばかりなので全然やり方が定まってません。 このレベルの物をこんなところに晒すのも何なのですが、とりあえずメモとして書いちゃいます。

LANG:ruby(linenumber)
#coding: utf-8
require 'test_helper'

class AccountControllerTest < ActionController::TestCase
  tests AccountController   # この例のように標準的な命名をしている限り、
                            # コントローラ名の指定はしなくてもOK

  # すべての context に共通の初期化処理
  setup {

    clear_all

    # ActionController::TestCase にて、以下のクラス変数が使われている。
    #
    #   @@required_fixture_classes: false
    #   @@already_loaded_fixtures: {}
    #   @@test_suites: {ActiveSupport::TestCase=>true, ...}
    #   @@current: #<AccountControllerTest:0xb192174>
    #
    # クラス変数を使う場合は、これらおよび未来の TestCase のクラス変数と
    # 名前が被らないよう注意する必要あり

    # setup とテストケース should の中とで情報をやりとりするのに
    # 普通にインスタンス変数 @xxx を使うか、反則技でクラス変数 @@xxx を
    # 使うのが良いか迷い中
    #
    # インスタンス変数を使うのが普通だが、
    #   - クラススコープからインスタンス変数にアクセスできなくて
    #     残念に感じることがたまにある
    #   - 比較的多数のインスタンス変数がフレームワークによって
    #     使われているため、命名の衝突が怖い
    #
    # クラス変数を使うのはかなり異端
    #   - 本来の目的からずれまくってるので完全に反則技
    #   - 初期化し忘れると直前のテストケースの値が残ってしまうのも問題
    #   - その代わり、どこからでもアクセスできるのがかなり便利?
    @@valid_account_params = { 
      name: "Foo Baa", 
      email: "account@example.com",
      affiliation: "My institute",
      address: "My address",
      telephone: "+00-000-0000"
    }
    
    @@invalid_account_params = {
      name: ''
    }
    
    @@valid_registration_params = {
      gender: 1,
      membership: 1,
      student: false,
      receipt_address: 'My address',
      payaccount: 'My Account'
    }
    
    # インスタンス変数として以下が定義されるので、名前が
    # 被らないように注意が必要
    #
    # インスタンス変数      利用時の記法   内容
    # @__name__             @__name__      現在実行中のテストのタイトルが入る
    # @__io__
    # @passed
    # @_assertions
    # @fixture_cache
    # @fixture_connections
    # @loaded_fixtures
    # @tagged_logger
    # @_partials
    # @_templates
    # @_layouts
    # @_files
    # @request              request        #<ActionController::TestRequest>
    # @response             response       #<ActionController::TestResponse>
    # @controller           @controller    #<Controller under testing>
    # @routes               @routes        #<ActionDispatch::Routing::RouteSet>
    # @shoulda_context
    # @html_document        html_document  #<HTML::Document>
    # @assigns              assigns        @controller 内で定義されたインスタンス変数
  }

  def clear_all
    ActionMailer::Base.deliveries.clear
    DatabaseRewinder.clean_all
    session.clear
    cookies.clear
  end
 
  # shoulda の redirect_to では、
  #   should redirect_to account_path
  #   should redirect_to account_path(action: :edit)
  #   should redirect_to url_for(controller: :account, action: :edit)
  # などとは書けないので、かわりに
  #   should redirect_to("account_path"){ account_path }
  #   should redirect_to("account_path(action: :edit)"){ account_path(action: :edit) }
  #   should redirect_to("controller: :account, action: :edit"){ 
  #               @controller.url_for(controller: :account, action: :edit) }
  # としても良いのだけれど、より簡単に
  #   should redirect_to_path :account
  #   should redirect_to_path :account, action: :edit
  #   should redirect_to_path controller: :account, action: :edit
  # などと書けるようにする
  def self.redirect_to_path(*arg)
    if arg.first.is_a? Symbol
      redirect_to_path_arg = arg.dup
      redirect_to_path_symbol = :"#{redirect_to_path_arg.shift}_path"
      if redirect_to_path_arg.empty?
        redirect_to(redirect_to_path_symbol){
          __send__(redirect_to_path_symbol)
        }
      else
        redirect_to_path_arg_s =redirect_to_path_arg.inspect.gsub(/\A\[|\]\z/,'')
        redirect_to_path_arg_s.gsub!(/\A\{|\}\z/,'') if redirect_to_path_arg.count == 1
        redirect_to("#{redirect_to_path_symbol}(#{redirect_to_path_arg_s})", &Proc.new {
          __send__(redirect_to_path_symbol, *redirect_to_path_arg)
        })
      end
    else
      redirect_to_path_arg = arg.dup
      redirect_to_path_arg_s =redirect_to_path_arg.inspect.gsub(/\A\[|\]\z/,'')
      redirect_to_path_arg_s.gsub!(/\A\{|\}\z/,'') if redirect_to_path_arg.count == 1
      redirect_to(redirect_to_path_arg_s){
        @controller.url_for *redirect_to_path_arg
      }
    end
  end
  
  # 以下すべてを ja, en 両方のロケールで試す
  [:ja,:en].each do |locale|
    context "with locale #{locale}," do
      setup {
        request.env['HTTP_ACCEPT_LANGUAGE'] = locale.to_s
      }
      
      # ここからが本当のテスト内容
      
      context "without login," do

        context "GET #new" do
          setup { 
            get :new 
          }
          should respond_with :success
          should render_template :new
          should_not set_the_flash
        end

        context 'GET #home' do
          setup { 
            get :home 
          }
          should redirect_to_path :account_login
          should set_the_flash
          should 'set session_intended_path to requested path' do
            # セッションキーは ApplicationController で 
            #   @session_intended_path="hogehoge"
            # として与えられているので、assigns を使って読み取る
            assert_equal @request.path, 
                         session[assigns(:session_intended_path)]
          end
        end
        
        context 'invalid POST #create' do
          setup { 
            post :create, account: @@invalid_account_params
          }
          should "not create account" do
            assert_equal 0, Account.count
          end
          should render_template :new
        end
        
        context 'valid POST #create' do
          setup {
            post :create, account: @@valid_account_params
          }
          should "create account" do
            assert_equal 1, Account.count
          end
          should redirect_to_path :account_login
          should "send email to registered address" do
            assert_not ActionMailer::Base.deliveries.empty?
            email_sent = ActionMailer::Base.deliveries.last
            assert_equal @@valid_account_params[:email], email_sent.to
          end
        end
        
      end

      context "with autologin," do

        setup {  
          clear_all
          @@account = Account.create!(
            @@valid_account_params.merge(
              password: Account.generate_new_password,
              auto_login: Account.generate_new_password
            )
          )
          
          cookies[assigns(:cookie_auto_login)] =
              [@@account.auto_login, @@account.email].join(':')
        }
        
        context 'GET #home' do
          setup { 
            get :home 
          }
          should respond_with :success
          should 'update autologin password at successful auto login' do
            assert_not_equal @@account.auto_login, assigns[:account].auto_login
            assert_equal assigns[:account].auto_login, 
                         cookies[assigns[:cookie_auto_login]].split(':',2)[0]
          end
        end
        
        context 'GET #new' do
          setup { 
            get :new
          }
          should set_the_flash  # already login
          should redirect_to_path :account
        end
        
        context 'GET #edit' do
          setup { 
            get :edit
          }
          should respond_with :success
        end
        
        context 'POST #update' do
          setup {
            post :update, account: {name: "Updated Name"}
          }
          should 'update name' do
            account = Account.find_by_email(@@account.email)
            assert_equal "Updated Name", account.name
          end
          should set_the_flash  # successfully updated
          should redirect_to_path :account
        end
        
        context 'DELETE #destroy' do
          setup {
            delete :destroy
          }
          should "delete account" do
            assert_equal 0, Account.count
          end
          should set_the_flash  # successfully deleted
          should redirect_to_path :default
        end
        
        context 'with valid registration,' do
          setup {
            @@account.registration =
              Registration.create!(@@valid_registration_params)
          }

          context 'DELETE #destroy' do
            setup {
              delete :destroy
            }
            should "not delete account" do
              assert_equal 1, Account.count
            end
            should set_the_flash  # can not delete
            should redirect_to_path :account
          end
        end

      end
    end
  end
  
end

コメント





Counter: 8397 (from 2010/06/03), today: 1, yesterday: 2