ResponseCodeMatchers.gem

https://github.com/r7kamura/response_code_matchers
夕方ぐらいにControllerのspecを書いていて「subject.code.should == "422"...」みたいなコードを書いていて、急に思い立って初めてCustom MatcherをつくってGemにした。

使い方

spec_helper.rbでこういう感じで設定すると使えるようになる。

# spec/spec_helper.rb
require "response_code_matchers"

RSpec.configure do |config|
  config.include ResponseCodeMatchers
end


今まではresponse.code.should == "422"って書いてたのを、こういう風に書けるようになる。Railsのcontrollerのspecだと、postやgetがresponseオブジェクトを返す。自分は大体subjectでpostやgetを呼ぶことが多いので、そういうときに良い感じに書ける。

# spec/controllers/blogs_controller.rb
describe BlogsController do
  describe "#create" do
    subject do
      post :create, params
    end

    let(:params) do
      { :title => "title", :body => "body", :token => "token", :user_id => 1 }
    end

    # 201
    context "with valid token" do
      it { should be_created }
    end

    # 400
    context "without user_id" do
      before do
        params.delete(:user_id)
      end

      it { should be_bad_request }
    end

    # 401
    context "with invalid token" do
      before do
        params[:token] = "invalid"
      end

      it { should be_unauthorized }
    end
  end
end

実装

RackがHTTPステータスコードと名前の対応表を持っているので、それを利用してMatcherをつくる。
https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L548

CustomMatcherは2通りの作り方がある。
1つはRSpec::Matchers.defineで簡単につくる方法。
1つはMatcher用のmodule + classを用意してあげる方法。

RSpec::Matchers.define :be_a_multiple_of do |expected|
  match do |actual|
    actual % expected == 0
  end
end


Matchersモジュールは、Matcherクラスのインスタンスを返すようなmatcherメソッドを定義すれば良い。
Matcherクラスは、以下のメソッドを持っていれば良い。

  • #matches?
  • #description
  • #failure_message
  • #negative_failure_message
require "rack"

module ResponseCodeMatchers
  Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |name, code|
    name = name.to_s.gsub("'", "")
    define_method("be_#{name}") do
      ResponseCodeMatcher.new(code.to_s, name)
    end
  end

  class ResponseCodeMatcher
    def initialize(expected, name)
      @expected    = expected
      @description = name.to_s.gsub("_", " ")
    end

    def matches?(response)
      @actual = response.code
      @actual == @expected
    end

    def description
      "be #@description"
    end

    def failure_message
      "expected response code to be #@expected, but #@actual"
    end

    def negative_failure_message
      "expected response code not to be #@expected, but #@actual"
    end
  end
end
広告を非表示にする