assert_selectの使い方

webアプリケーションは、結局のところ適切なHTML文書を返すサーバーなので、最終的にはレスポンスとして返すHTMLが予想通りであれば良いということになる。ところが、HTMLを比較する時には単純に文字列として比較するのは問題がある。ブラウザの描画には影響しないコメントや改行、スペース、タブが含まれているからだ。確認したいのはwebブラウザの描画結果が適切なこと、つまりHTMLの構造が適切かどうか。
その確認をするために、Railsには「assert_select」メソッドが用意されていた。assert_selecteを利用すると、CSSセレクタで指定する要領でHTMLの構造が適切かどうか、効率よく調べることができる。assert_selectは幅広い応用力を持っている!が、ちゃんと理解して使いこなすのはちょっと大変そうだ。以下は自分がひたすら試してみた、例題とその動き。

書式

  • 現在表示しているページの一部をセレクタで特定して、確認条件が満たされているかどうかを確認する。
# assert_select("セレクタ", 確認条件, "失敗したときのメッセージ")

確認条件の例

# 存在
assert_select "form" # formタグが、1以上存在すれば成功。
assert_select "form", false # formタグが存在しなければ成功。

# 数
assert_select "form input", 4 # formタグ以下のinputタグの数が、4であれば成功。

# 範囲
assert_select "form input", 1..4 # formタグ以下のinputタグの数が、1〜4であれば成功。

# 文字列
assert_select "title", "Welcome" # titleタグの文字列が、「Welcome」と一致すれば成功。

# 正規表現
assert_select "title", /Welcome/ # titleタグの文字列に、「Welcome」が含まれていれば成功。

# ハッシュ
assert_select "title", {:count=>5, :text=>"Welcome"} # titleの、タグ数が5 and テキストが「Welcome」であれば成功。
  # ちなみに:countに代わって以下のシンボルも指定も可能。
assert_select "title", :minimum=>5 # titleタグ数が少なくても5個あれば成功。(5個以上なら成功)
assert_select "title", :muximum=>5 # titleタグ数が最大5個までなら成功。(5個以下なら成功)

セレクタの例

タグ間の関係
  # 「 」スペースは親と子以下の関係すべてを表す。
assert_select "body div.header"
  # 以下の構造が存在すれば成功。
  # bodyとdivの関係は、親と子・孫・ひ孫...の差があれば階層は問わない。
  # 「.」はclass属性を表す。
  #   <body>
  #     ...
  #       <div class="header">
  #         ...
  #       </div>
  #     ...
  #   </body>

  # ちなみに上記の関係は、ブロックで表現することも可能。
assert_select "body" do
  assert_select "div.header"
end

  # 「>」は親子関係を表す。
assert_select "ol > li#?", /item-\d+/
  # 以下の構造が存在すれば成功。
  # olとliの関係は、親子関係のみ。
  # 「#」はid属性を表す。liのid属性は正規表現「/item-\d+/」にマッチする「item-数値」の形式である。
  #   <ol>
  #     <li id="item-1"></li>
  #     <li id="item-2"></li>
  #     ...
  #     <li id="item-10"></li>
  #     ...
  #   </ol>

  # ちなみに上記のHTMLの関係は、以下のようにも表現できる。
  # 「~」は兄弟関係を表す。順序にも意味がある。
assert_select "li#item-1 ~ li#item-10" # 兄弟関係なので成功。

  # 「+」は隣同士の兄弟関係を表す。順序にも意味がある。
assert_select "li#item-1 + li#item-2"  # 隣同士なので成功。
assert_select "li#item-1 + li#item-10" # 離れているので失敗。

  # 「,」で区切れば、複数のセレクタ条件を指定できる。
assert_select "li#item-0, li#item-1" # 少なくとも片方が存在するので成功。
assert_select "li#item-0, li#item"   # どちらも存在しないので失敗。
タグの属性
assert_select "form input[name]"
  # formタグ以下に、name属性を持つinputタグが存在すれば成功。
  # [属性名]で属性を表現できる。

assert_select "form input[name=slip]"
  # formタグ以下に、name="slip"属性を持つinputタグが存在すれば成功。
  # 比較の記号文字によって、以下のような条件にもなる。
assert_select "form input[name^=slip]" # slipで始まる。
assert_select "form input[name$=slip]" # slipで終わる。
assert_select "form input[name*=slip]" # slipが含まれる。
assert_select "form input[name~=slip]" # スペース区切りの属性値の一つがslipである。
assert_select "form input[name|=slip]" # スペース区切りの属性値の先頭がslipである。

assert_select "form input[name=slip][id=slip-1]"
  # formタグ以下に、name="slip"属性とid="slip-1"属性の両方を持つinputタグが存在すれば成功。
  # 複数の属性を指定可能。指定した全ての属性を持っていれば成功。

assert_select "form input[class=slip]"
assert_select "form input.slip"
  # 上記は同等

assert_select "form input[id=slip]"
assert_select "form input#slip"
  # 上記は同等

assert_select "form input[id^=slip]"
assert_select "form input#?", /^slip/
  # 上記は同等
タグの構成
assert_select "div p:only-child"        # "div p"にマッチした<p>タグが、唯一の子なら成功。
assert_select "div p:first-child"       # "div p"にマッチした<p>タグが、最初の子なら成功。
assert_select "div p:last-child"        # "div p"にマッチした<p>タグが、最後の子なら成功。
assert_select "div p:nth-child(2)"      # "div p"にマッチした<p>タグが、2番目の子なら成功。
assert_select "div p:nth-last-child(3)" # "div p"にマッチした<p>タグが、後ろから3番目の子なら成功。

assert_select "div p:only-of-type"        # "div p"にマッチした<p>タグが、<p>タグとしては一つだけであれば成功。
assert_select "div p:first-of-type"       # "div p"にマッチした<p>タグが、<p>タグとしては最初であれば成功。
assert_select "div p:last-of-type"        # "div p"にマッチした<p>タグが、<p>タグとしては最後であれば成功。
assert_select "div p:nth-of-type(2)"      # "div p"にマッチした<p>タグが、<p>タグとしては2番目であれば成功。
assert_select "div p:nth-last-of-type(3)" # "div p"にマッチした<p>タグが、<p>タグとしては後ろから3番目であれば成功。

  # <div>
  #   <p>test</p>
  #   <ul>
  #     <li>list1</li>
  #     <li>list2</li>
  #   </ul>
  # </div>
  # 上記HTMLをテストすると...
assert_select "div p:only-child"   # "div p"にマッチする<p>タグは、唯一の子でないから失敗。
assert_select "div p:only-of-type" # 子要素は2だが、<p>タグとしては1だけなので成功。

  # <div>
  #   <p>
  #     <span></span>
  #   </p>
  # </div>
  # 上記HTMLをテストすると...
assert_select "div span:only-child" # "div span"にマッチする<span>タグは、唯一の子であるから成功。
assert_select "div>span:only-child" # "div>span"にマッチする<span>タグは、存在しないので失敗。

  # <div>
  #   <span id="test1"></span>
  #   <p>
  #     <span id="test2"></span>
  #   </p>
  # </div>
  # 上記HTMLをテストすると...
assert_select "div span#test1:only-child" # "div span#test1"にマッチする<span>タグは、唯一の子でないから失敗。
assert_select "div span#test2:only-child" # "div span#test2"にマッチする<span>タグは、唯一の子であるから成功。
assert_select "div span:only-child"       # "div span"にマッチする<span>タグは、<span id="test2">がマッチして成功。


assert_select "div#flash:empty" # <div id="flash"></div>のようにタグの中身が空の状態なら成功。
assert_select "html:root" # <html>タグがそのページのルートであれば成功。 

assert_select_rjs

書式
  • xml_http_requestによって部分更新した内容をセレクタで特定して、確認条件が満たされているかどうかを確認する。
assert_select_rjs(id)
assert_select_rjs(:method, id)
assert_select_rjs(:insert, :position, id)
利用例
assert_select_rjs :remove, "j123456789"
  # xml_http_requestによって、id="j123456789"が削除されたら成功。

assert_select_rjs :replace, "journals_footer" do
  assert_select 'th', 3
  assert_select "th input[value=1,000]", 1
end
  # xml_http_requestによって、id="journals_footer"の範囲で更新された内容についてブロック内の処理をする。
  # 上記の対象が存在すれば成功。存在しなければ失敗。

assert_select_rjs :insert, :before, "journals_footer" do
  assert_select 'th', 2
  assert_select 'td', 2
end
  # xml_http_requestによって、id="journals_footer"の範囲で:beforeの位置に:insertされた内容についてブロック内の処理をする。
  # 上記の対象が存在すれば成功。存在しなければ失敗。


なんだかassert_select自体のテストを自動処理したい気分だ...。まだ、すべての機能を網羅できていない。