light log

学んだこととか

RSpecを導入し始めたらrequireとか調べてた

(今日も作業ログ的日記的文章)

昨日に引き続きRSpecやってた。いよいよ今日はspecを書き始めた。

でもすぐにはまった。

経緯(というか全部をベタ書き)

前に作った勉強用アプリのspecを書き始めてみた。

RSpecの本家サイトのドキュメントとかを参考にしながら書いていると、まずRails Tutorialで使っていたRSpecとのバージョンが違うことで、使い方が結構変わっていることに戸惑った。本題とは関係ないけど)

そのアプリでは、sentencesっていうリソースを以下のようにルート定義してるんだけど、

resources :sentences, only: [:index, :show, :edit, :update]

以下のspecのgetがうまく動かなくてはまった。

  describe 'GET #show' do
    it 'renders the show template' do
      sentence1 = Sentence.create!
      get :show
      expect(response).to render_template('show')
    end
  end

答えは/setences/:id の:idを指定していないからで、今思えば当たり前に思える。

とにかくこのときは理由がわからなくて(get :indexは動いてたので)、getのマニュアルを探した。

まず以下のrspec-railsのドキュメントに当たったんだけど、書いてない。そもそもあんまり網羅的に書いてある類のドキュメントじゃない。しかもなんでCSSが適用されてないみたいな見た目になってるんだろう。

File: README — Documentation by YARD 0.8.7.3

次に、Railsメソッドかな?と思ってRailsAPIを見たら、あった。

http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-get

ActionController::TestCase::Behavior#getメソッド

でも本当にこのgetなのかいまいち確信が持てなくて、特定のメソッドがどのクラスに属するものなのか調べる方法はないのか、調べてみた。

Rubyでメソッドの定義場所を見つける方法 - Qiita

ずばりな記事。

こういったケースでは、Kernel#methodとMethod#source_locationを組み合わせることで、メソッドの定義場所を見つけることができます。

なるほど。

つまり、specに

p method(:get).source_location

を書けばよさそう。

でもspecにこれを書いて標準出力で確認するのはなんかクールじゃないなと思って、rails consoleでできないだろうかと思った。

$ rails c
Loading development environment (Rails 4.2.1)
irb(main):001:0> require 'rails_helper'
LoadError: cannot load such file -- rails_helper

だめ。これはまぁ想定通り。

irb(main):004:0> require './spec/rails_helper'
LoadError: cannot load such file -- spec_helper

rails_helperは読めたけど、その中で読み込んでるspec_helperが読めない。spec_helperはパスじゃなく名前だけで指定されてるので、それも読み込むためにはrequireがファイルを探す仕組みを知らなければいけない。

調べたらすぐ出てきた。$LOAD_PATHにディレクトリのリストが格納されていて、それを順にたどるらしい。なので、ここにspecディレクトリを追加してやればいい。

$LOAD_PATH == $:のようなので、先頭に追加するには、

irb(main):010:0> $:.unshift(File.expand_path('./spec'))

これでいけた。requireできるだろうか。

irb(main):011:0> require 'rails_helper'
NoMethodError: undefined method `configure' for RSpec:Module

なんか別のエラーが出た。そしてこれは根が深そう。RSpecの起動の仕組みを知らないと解決できなさそう。

というか、よく考えると、getの正体はすでにActionController::TestCase::Behavior#getではないかという予想はあって、その確信を得るために確認しようとしているんだけど、実際に使用している環境(spec)とは別の環境(rails console)で確認しても結局確信はできないような。

ということで大人しくspecの中にpを入れることにした。

p method(:get).source_location
# => #<Method: RSpec::ExampleGroups::SentencesController::GETShow(ActionController::TestCase::Behavior)#get>

前半部分はRSpecによって自動的に作られているものだろうか。そこはよくわかってないものの、とりあえず括弧の中に疑惑のActionController::TestCase::Behaviorを発見。

やはりこいつだったということで、RailsAPIを見たらget(action, *args)となってて、第二引数にパラメータのハッシュを渡す模様。そりゃそう。

ということで、こうしたらうまくいった。

get :show, id: 1

まとめ

頭悪くていろいろ大変。

参考

Rubyにおけるロードパス(require)のtips | Futurismo