読者です 読者をやめる 読者になる 読者になる

おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Chapter 17 Extending RSpec その3

Chapter 17 Extending RSpec その2の続き。

17.4 Macros

下記のコードを

describe Widget do
  it "requires a name" do
    widget = Widget.new widget.valid?
    widget.should have(1).error_on(:name)
  end
end

カスタムマッチャを使って極限まで省略すると下記のような感じに出来る。(shouldのレシーバを省略すると、describeのオブジェクトをnewしたものが補完される。)

describe Widget do
  it { should require_attribute(:name) }
end

これをmacroを使うともっとよくできる。macroはrspecの代替プラグインshouldaから来ている。

describe Widget do
  it_validates_presence_of Widget, :name
end

shared example groupに似ているけど、独自の名前をもてるところと引数を渡せるところが違う。下記のような controller の examlple があるとすると

describe ProjectsController do
  context "handling GET index" do
    it "should render the index template" do
      get :index 
      controller.should render_template("index")
    end
    it "should assign @projects => Project.all" do
      Project.should_receive(:all).and_return(['this array'])
      get :index 
      assigns[:projects].should == ['this array']
    end
  end
end

macroを使うと下記のように書けるようになる。スッキリ!

describe ProjectsController do
  get :index do
    should_render "index"
    should_assign :projects => [Project, :all]
  end
end

macroは下記のような感じで定義する

module ControllerMacros
  def should_render(template)
    it "should render the #{template} template" do
    do_request
    response.should render_template(template)
    end
  end

  def should_assign(hash)
    variable_name = hash.keys.first
    model, method = hash[variable_name]
    model_access_method = [model, method].join('.')
    it "should assign @#{variable_name} => #{model_access_method}" do
      expected = "the value returned by #{model_access_method}"
      model.should_receive(method).and_return(expected)
      do_request
      assigns[variable_name].should == expected
    end
  end

  def get(action)
    define_method :do_request do
      get action
    end
    yield
  end
end

Spec::Runner.configure do |config|
  config.use_transactional_fixtures = true
  config.use_instantiated_fixtures = false
  config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
  config.extend(ControllerMacros, :type => :controller)
end

全部のexample groupでControllerMacrosモジュールを使いたくない場合は、下記のように明示的にマクロをextendするといい。

describe ProjectsController do
  extend ControllerMacros
  # ...
end

17.5 Custom Formatters

spec実行後のメッセージのフォーマットをカスタムする方法について。今のところbuilt-inで満足しているので省略。