桐生あんずです

日常やプログラミングについて書いています。

HTMLのタグの中身をRailsのminitestを使って確認する書き方

桐生あんずです。
先日インターン先でSEO対策のために記事の詳細ページなどのmetaタグの中身(descriptionやog、TwitterCardsなど)を動的に変化させるコードを書きました。*1
Chromeの検証のおかげでちゃんと中身が変化していることはわかったけど、「せっかくだからテストコード書きたいよね〜!」という指令が出たので、Railsのminitestを使ってちゃんとmetaタグの中身がページ別に反映されているかどうかをチェックするテストコードを書くことになりました。
なおテストコードを自分で書くのはこれが初めてです。頑張っていきましょう。
Railsのバージョンは5.1.2、Rubyのバージョンは2.4.1です。
まず、Railsチュートリアルを参考に見ていくとふむふむ、「assert_select 」を使って対象のタグ名をチェックするんだな…?

assert_select "title", "Home | Ruby on Rails Tutorial Sample App"

じゃあ、これをmetaで置き換えてname属性の中身やproperty属性の中身を確認していけばいいじゃん!やってみよう!
と思ってまず書いてみました。

test "should be able to show topics meta tag" do
  get topic_path(topics(:one))
  assert_select "meta", {:property =>/og:title/, :content => /test/}
  assert_select "meta", {:property => /og:description/, :content => /hogehoge/ }   assert_select "meta", {:property => /og:url/, :content=>/topics_path(:one)/}
  #oneはfixtureで既に用意されたインスタンスのデータ
  assert_select "meta", {:name => /twitter:title/, :content => /test/}
  assert_select "meta", {:name => /twitter:description/, :content => /hogehoge/}
  assert_select "meta", {:name => /description/, :content=> /hogehoge/}
end

そしてテストしてみる。
通った!やったー!完!
で、終わると思ったか?
minitestに詳しい方はわかると思いますがこれは残念ながらザルテストです。content属性の中身が目的のもの以外になっていても通ります。理由はちゃんとわかりませんが、予想ではproperty属性やname属性の中身が合っているとそれでOKと判断されてしまうとかなのかなあ。

そこで、いい感じに判定してくれる記述はないものかと漁っているうちに。
効率的な動作確認の方法を求めて - ザリガニが見ていた...。
こんな記事がありました。(10年前だ…)
投稿時期はかなり古いですがもしかしたら何かヒントはあるかもしれないと思ったので探してみます。(探していてわかったことですが、minitestに関わる文献は日本語の新しい記事がめちゃくちゃ少ない。厳しい。)

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

これで両方の属性をちゃんとテストすることができるのでは!?
やってみましょう。

test "should be able to show topics meta tag" do
  get topic_path(topics(:one))
  assert_select 'meta[name*=?][content*=?]', "description","hoge", { :count => 2 }
  assert_select 'meta[name*=?][content*=?]', "twitter:title","test", { :count => 1 }
  assert_select 'meta[name*=?][content*=?]', "twitter:description","hoge", { :count => 1 }
  assert_select 'meta[property*=?][content*=?]', "og:title","test", { :count => 1 }
  assert_select 'meta[property*=?][content*=?]', "og:description","hoge", { :count => 1 }
  assert_select 'meta[property*=?][content*=?]', "og:url","#{topics(:one).id}", { :count => 1 }
end
#[content*=?]という書き方で、指定した文字列がcontent属性の中身の一部に含まれているかどうかを探すことができる

ここで、細かい確認をするためにもbyebugをassert_selectの前に書き、指定したページのname属性やproperty属性の中身を確認しておきましょう。byebugが起動した際に「response」と入力すれば指定したページのviewの中身を確認ことができます。

確認したのちに、テストを動かしてみると…、無事通りました。
確認として、間違った中身を入れてみるとちゃんと失敗してくれます。本当によかった。
指定したHTMLタグの複数の属性の中身を確認したい場合、このような記述でテストコードを書けば良いことがわかりました。
初めてテストコードを書いたこともあり、ここまでいくのに4時間ほどかかりましたが、学ぶことはかなり多かったです。記述の調べ方もちょっとだけわかった気がするので新しいテストコードをどんどん書いていきたいです。
初めて技術記事っぽいものを書いたのでちゃんと書けているか緊張していますが、読んでいただいた方ありがとうございました。

おまけ contentの中身の画像URLの確認の仕方


まだ気になることがある方がいると思います。og:imageやTwitterCardsのcontentの中の画像URLが合っているどうかも確認したいですよね。
そこで、画像URLの中身を確認するテストを別に書くことにしました。

  test "should be able to show topic meta image tag" do
    log_in_as(@user)
    image = fixture_file_upload('test.jpg', 'image/jpg')

    assert_difference 'Topic.count', 1 do
      post topics_path, params: {topic: {title: "hi", content: "message1", category_id: 1, photo_attributes: {image: image}, user: @user}}
    end
    
    topic = assigns(:topic)
    assert_equal "test.jpg" , topic.photo.image_file_name
    get topic_path(topic)
    assert_select 'meta[content*=?]', "test.jpg", { :count => 2 }
  end
#topicモデルとphotoモデルの関係は1対1

paperclipで画像を生成している際の書き方です。
fixture_file_uploadで画像をテスト内で生成します。
また、fixturesのフォルダに同名の画像ファイルを突っ込んでおかないといけません。
assignsでオブジェクトを取得した後、ちゃんと画像が入っているか確認。
その後テストしたいページで、先ほどの記述と同じようにcontent属性の中に指定した画像ファイルが含まれているかどうかを確認。(:count => 2になっているのはog:imageとtwitter:imageの2つを確認したいから。)
これで画像の中身が動的に変化しているか確認できます。ありがとうございました。

*1:metaタグを動的に変化させるやり方の一例はこんな感じです。 【Rails】『meta-tags』gemを使ってSEO対策をおこなう方法 | vdeep