Everyday Rails - RSpecによるRailsテスト入門 chapter9

最近、積読状態だった以下を読んでいます。
RSpec学ぶにはなかなか良い本だと思います。
9章の内容が良かったのでご紹介。
leanpub.com

9. 速くテストを書き、速いテストを書く

aggregate_failures

aggregate_failures を使えば、事前処理が同じようなitをまとめて書くことができます。

      it "responds successfully" do
        sign_in @user
        get :index
        aggregate_failures do
          expect(response).to be_success
          expect(response).to have_http_status "200"
        end
      end

aggregate_failures がない状態だと、どこで落ちたかの判別ができないため、従来は it を分けて書くことが推奨されてきました。
しかし、aggregate_failuresを使えば、途中でエラーとなっても、最後までexpectを実行しどのようなエラーがどこで発生したかが分かりやすくなります。

エラー例)

Failures:

  1) ProjectsController#index as an authenticated user responds successfully
     Got 2 failures from failure aggregation block.
     # ./spec/controllers/projects_controller_spec.rb:13:in `block (4 levels) in <top (required)>'

     1.1) Failure/Error: expect(response).to_not be_success
            expected `#<ActionDispatch::TestResponse:0x00007fdf6580fce0 @mon_mutex=#<Thread::Mutex:0x00007fdf6580fc68>, @mo...ders:0x00007fdf65829ca8 @req=#<ActionController::TestRequest:0x00007fdf6580fe98 ...>>, @variant=[]>>.success?` to return false, got true
          # ./spec/controllers/projects_controller_spec.rb:14:in `block (5 levels) in <top (required)>'

     1.2) Failure/Error: expect(response).to have_http_status "201"
            expected the response to have status code 201 but it was 200
          # ./spec/controllers/projects_controller_spec.rb:15:in `block (5 levels) in <top (required)>'

こりゃ使わない手はないでしょう。

doubleとinstance_double

doubleとinstance_doubleの違いについて記載があり、わかりやすい例があったのでご紹介。  
1対多の関係のこう言うモデルがあったとします。 f:id:rochefort:20200531182812p:plain

class User < ApplicationRecord
  has_many :notes

  def name
    [first_name, last_name].join(" ")
  end
end


class Note < ApplicationRecord
  belongs_to :user

  delegate :name, to: :user, prefix: true
end

Noteにuserモデルにdelegateさせるメソッドを生やしていて、これをテストする場合。 通常は、FactoryBotでデータ作成してテストします。

  it "delegates name to the user who created it" do
    user = FactoryBot.create(:user, first_name: "Fake", last_name: "User")
    note = Note.new(user: user)
    expect(note.user_name).to eq "Fake User"
  end

doubleを使うことで、DBへのアクセスを無くしてテストすることができます。

  it "delegates name to the user who created it" do
    user = double("user", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

一見良さそうに見えますが、例えば、User#name の名称が full_nameなどに変わったとしても テストは成功してしまいます。そこで登場するのが、 instance_double

  it "delegates name to the user who created it" do
    user = instance_double("User", name: "Fake User")
    note = Note.new
    allow(note).to receive(:user).and_return(user)
    expect(note.user_name).to eq "Fake User"
  end

これを使うと、Userモデルに定義されているかどうかまで検証してくれます。(第一引数が大文字になっています。)

See Also

aggregate_failuresやinstance_doubleについても自分のブログで少し言及していたことを忘れていました。。
RSpecのベストプラクティスとRSpec3の新機能 - rochefort's blog http://rochefort.hatenablog.com/entry/2016/02/16/080000

MacのKey mapping変更にはKarabiner-Elementsが便利

karabiner-elements.pqrs.org

 
以前からあったのですが、当時はまだ複数keyのmappingに対応しておらず、やむなくhammer spoonを利用していました。
いろんな局面で Ctrl+J/K を矢印の代わりに使いたいという要望があり、その実現のために、以前はKey Bindingツールとして、hammer spoonで凌いでいました(便利は便利だったのですが、karabinerの方がさくっとできる)。
Hammerspoon が面白い - rochefort's blog
改めて確認したところ、現在は問題なく利用できるようになっていました。

設定

(※画像は既に設定済み)

  1. Complex modifications
    f:id:rochefort:20200531174024p:plain

  2. Add Ruleを選択
    f:id:rochefort:20200531174157p:plain

  3. Import more rules from the Internet(open a browser) をクリック
    ブラウザでルールの検索ができる

  4. vimで検索し、それっぽいのを選択 f:id:rochefort:20200531174426p:plain

以上。

(rails)いつの間にかannotateが動かなくなっていた

ctran/annotate_models: Annotate Rails classes with schema and routes info

こんな感じで、migrateのタイミングで自動的にモデル関連ファイルにannotate(コメント)を追加してくれるgemです。
これがいつの間にか動かなくなっていました。

# == Schema Info
#
# Table name: line_items
#
#  id                  :integer(11)    not null, primary key
#  quantity            :integer(11)    not null
#  product_id          :integer(11)    not null
#  unit_price          :float
#  order_id            :integer(11)
#

class LineItem < ActiveRecord::Base
  belongs_to :product
  . . .

原因

ctran/annotate_models: Annotate Rails classes with schema and routes info https://github.com/ctran/annotate_models#upgrading-to-3x-and-annotate-models-not-working

ここに書いている通りですが、version3からmodelsオプションが追加され、これをtrueにしないと動作しなくなっていました。

注意点

別途classified-sortオプションも追加されており、これがdefaultになっています。

Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns

id, その他のカラム、timestamp、assosiationの順でannotateが追加されるとのこと。 version2と同じ挙動にしたい場合(DBと同じ)は、falseにすると良いです。