RSpec テストコード書き方 サンプル _φ(・・*)

今回は、テストコードの書き方メモをまとめておこうと思います。
まだ、RSpecのコードを書き始めたばかりなので、リファクタリングが必要な箇所があるかと思いますm(_ _)m

※設定等の準備編は下記にまとめました。
[RSpecテスト環境 設定 メモ _φ(・・*) - Qiita](http://qiita.com/hitomi_/items/fc9e9a3aa68039d68776)

## 環境

・Rails 4.1.4
・ActiveRecord 4.1.4
・Mongoid 4.0.0
・Rspec 3.1


## テストデータ FactoryGirl
※モデル名等はここにあげるにあたり、適当なのに変えていてあまり意味はありません。

```rb
FactoryGirl.define do
  factory :task do
    sequence(:seq) {|n| n}
    good false
    done false
    comment "MyString"
    memo "MyString"

    factory :invalid_task do
      good nil
    end
  end
end

```

## ログインユーザーの対応

※参考:[Rails+Devise+Rspecでログインが必要なControllerをテスト](http://blog.sanojimaru.com/post/20934471686/rails-devise-rspec%E3%81%A7%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%AAcontroller%E3%82%92%E3%83%86%E3%82%B9%E3%83%88)

```rb
#spec/rails_helper.rb
  config.include Devise::TestHelpers, type: :controller
  config.extend ControllerMacros, :type => :controller
```

```rb
#spec/support/controller_macros.rb
module ControllerMacros
  def login_user
    before(:each) do
      @request.env["devise.mapping"] = Devise.mappings[:user]
      sign_in FactoryGirl.create(:user)
    end
  end

  def login_admin
    before(:each) do
      @request.env["devise.mapping"] = Devise.mappings[:admin]
      sign_in FactoryGirl.create(:admin)
    end
  end
end
```

## modelの基本的なところ
```rb
# 特殊なところ等、メソッドなど省略しています

require 'rails_helper'

RSpec.describe Task, :type => :model do

  describe "FactoryGirl be valid" do
    it { expect(build(:task)).to be_valid }
  end

  it { is_expected.to be_timestamped_document }
  it { is_expected.to have_index_for(seq: 1) }

  describe "have fields" do
    it { is_expected.to have_fields(:good).of_type(Mongoid::Boolean) }
    it { is_expected.to have_field(:done).of_type(Mongoid::Boolean).with_default_value_of(false) }
    it { is_expected.to have_fields(:comment, :memo).of_type(String) }
  end

  describe "Association be valid" do
    it { is_expected.to belong_to(:note) }
  end

  describe "Validation be valid" do
    it { is_expected.to validate_presence_of(:good) }
  end

  describe "scope be valid" do
    it "order_default" do
      first_three_item_createds = Task
        .desc(:created_at).limit(3).pluck(:created_at)
      expect(Task
        .order_default.limit(3).pluck(:created_at))
        .to match_array first_three_item_createds
    end
  end
end
```

## Controllerの基本的なところ
```rb
# 特殊なところ等、一部省略しています

require 'rails_helper'

RSpec.describe Admins::TasksController, :type => :controller do
  describe "GET #index" do
    login_admin
    before(:each){ create_list(:task, 3)}
  
    context "デフォルト(条件指定のパラメータがない場合)" do
      before(:each){ get :index }

      it "解決済み(done: true)は返さない" do
        create(:task, done: true)
        expect(assigns(:admins_tasks).pluck(:done))
          .to_not match_array([true])
      end

      it "返される数は、20件以下のlimitがかかっていること" do
        create_list(:task, 21)
        expect(assigns(:admins_tasks).count(true) <= 20).to eq true
      end
    end

    it "params[:note_id]で指定があれば、それを返すこと" do
      note = create(:note)
      task = create(:task, note_id: note.id)
      get :index, note_id: note.seq
      expect(assigns(:admins_tasks).count).to eq 1
    end

    describe "params[:search]で検索条件があれば、それを返すこと" do
      it "コメント検索" do
        comment = "コメント"
        task = create(:task, comment: comment)
        get :index, search: {comment: comment}
        expect(assigns(:admins_tasks).last.comment).to eq comment
      end

      it "メモ検索" do
        memo = "メモ"
        task = create(:task, memo: memo)
        get :index, search: {memo: memo}
        expect(assigns(:admins_tasks).last.memo).to include memo
      end
    end

    it "renders ther :index template" do
      get :index
      expect(response).to render_template :index
    end
  end

  describe "POST #create" do
    context "with valid attributes" do
      it "saves the new record in the DB" do
        expect{
          xhr :post, :create, task: attributes_for(:task, note_id: note.id)
        }.to change(Task, :count).by(1)
      end

      it '@task_note_seq be valid' do
        xhr :post, :create, task: attributes_for(:task, note_id: note.id)
        expect(assigns(:task_note_seq)).to eq note.seq
      end
    end

    context "with invalid attributes" do
      it "does not save the new record in the DB" do
        expect{
          xhr :post, :create, task: attributes_for(:invalid_task, note_id: note.id)
        }.not_to change(Task, :count)
      end
    end
  end


  describe "PATCH #update" do
    login_admin
    let(:task) { create(:task) }

    context "valid attributes" do
      it "locates the requested" do
        xhr :patch, :update, id: task, task: attributes_for(:task)
        expect(assigns(:task)).to eq(task)
      end

      it "changes attributes :done" do
        xhr :patch, :update, id: task,
          task: attributes_for(:task, done: true)
        task.reload
        expect(task.done).to eq true
      end

      it "changes attributes :memo" do
        memo = "メモ"
        xhr :patch, :update, id: task,
          task: attributes_for(:task, memo: memo)
        task.reload
        expect(task.memo).to include memo
      end
    end

    context "with invalid attributes" do
      it "does not change" do
        xhr :patch, :update, id: task,
          task: attributes_for(:task, done: "test")
        task.reload
        expect(task.done).not_to eq "test"
      end
    end
  end
end

```

まだまだとりあえず書いてみたというレベルなので、
これから、もっと慣れていきたいと思います。

## 追記: `rails g`でcontrollerのrspecテンプレも作られる

configでg.test_frameworkの設定をきちんとかいたからか、
気がついたら、新しいprojectで最初から、きちんと設定かいていったら、
rails generate でできるrspecのテンプレが素敵だったので、追記。
初心者に優しい☆

config/application.rb

```.rb
    config.generators do |g|
      g.test_framework :rspec,
        fixtures: false,
        view_specs: false,
        helper_specs: false,
        routing_specs: false,
        controller_specs: true,
        request_specs: false
      g.fixture_replacement :factory_girl, dir: "spec/factories"
    end
```


```
$ rails g scaffold_controller Articles
```

spec/controllers/articles_controller_spec.rb

```.rb
require 'rails_helper'

# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator.  If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails.  There are a number
# of articles you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec.  Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.

RSpec.describe ArticlesController, :type => :controller do

  # This should return the minimal set of attributes required to create a valid
  # Article. As you add validations to Article, be sure to
  # adjust the attributes here as well.
  let(:valid_attributes) {
    skip("Add a hash of attributes valid for your model")
  }

  let(:invalid_attributes) {
    skip("Add a hash of attributes invalid for your model")
  }

  # This should return the minimal set of values that should be in the session
  # in order to pass any filters (e.g. authentication) defined in
  # ArticlesController. Be sure to keep this updated too.
  let(:valid_session) { {} }

  describe "GET index" do
    it "assigns all articles as @articles" do
      article = Article.create! valid_attributes
      get :index, {}, valid_session
      expect(assigns(:articles)).to eq([article])
    end
  end

  describe "GET show" do
    it "assigns the requested article as @article" do
      article = Article.create! valid_attributes
      get :show, {:id => article.to_param}, valid_session
      expect(assigns(:article)).to eq(article)
    end
  end

  describe "GET new" do
    it "assigns a new article as @article" do
      get :new, {}, valid_session
      expect(assigns(:article)).to be_a_new(Article)
    end
  end

  describe "GET edit" do
    it "assigns the requested article as @article" do
      article = Article.create! valid_attributes
      get :edit, {:id => article.to_param}, valid_session
      expect(assigns(:article)).to eq(article)
    end
  end

  describe "POST create" do
    describe "with valid params" do
      it "creates a new Article" do
        expect {
          post :create, {:article => valid_attributes}, valid_session
        }.to change(Article, :count).by(1)
      end

      it "assigns a newly created article as @article" do
        post :create, {:article => valid_attributes}, valid_session
        expect(assigns(:article)).to be_a(Article)
        expect(assigns(:article)).to be_persisted
      end

      it "redirects to the created article" do
        post :create, {:article => valid_attributes}, valid_session
        expect(response).to redirect_to(Article.last)
      end
    end

    describe "with invalid params" do
      it "assigns a newly created but unsaved article as @article" do
        post :create, {:article => invalid_attributes}, valid_session
        expect(assigns(:article)).to be_a_new(Article)
      end

      it "re-renders the 'new' template" do
        post :create, {:article => invalid_attributes}, valid_session
        expect(response).to render_template("new")
      end
    end
  end

  describe "PUT update" do
    describe "with valid params" do
      let(:new_attributes) {
        skip("Add a hash of attributes valid for your model")
      }

      it "updates the requested article" do
        article = Article.create! valid_attributes
        put :update, {:id => article.to_param, :article => new_attributes}, valid_session
        article.reload
        skip("Add assertions for updated state")
      end

      it "assigns the requested article as @article" do
        article = Article.create! valid_attributes
        put :update, {:id => article.to_param, :article => valid_attributes}, valid_session
        expect(assigns(:article)).to eq(article)
      end

      it "redirects to the article" do
        article = Article.create! valid_attributes
        put :update, {:id => article.to_param, :article => valid_attributes}, valid_session
        expect(response).to redirect_to(article)
      end
    end

    describe "with invalid params" do
      it "assigns the article as @article" do
        article = Article.create! valid_attributes
        put :update, {:id => article.to_param, :article => invalid_attributes}, valid_session
        expect(assigns(:article)).to eq(article)
      end

      it "re-renders the 'edit' template" do
        article = Article.create! valid_attributes
        put :update, {:id => article.to_param, :article => invalid_attributes}, valid_session
        expect(response).to render_template("edit")
      end
    end
  end

  describe "DELETE destroy" do
    it "destroys the requested article" do
      article = Article.create! valid_attributes
      expect {
        delete :destroy, {:id => article.to_param}, valid_session
      }.to change(Article, :count).by(-1)
    end

    it "redirects to the articles list" do
      article = Article.create! valid_attributes
      delete :destroy, {:id => article.to_param}, valid_session
      expect(response).to redirect_to(articles_url)
    end
  end

end

```

## 参考リンク

### 本
rspec 解説本(電子書籍)
https://leanpub.com/everydayrailsrspec-jp/read
└→本のsample > everydayrails/rspec_rails_4
　https://github.com/everydayrails/rspec_rails_4

### gem 解説
rspec-rails
https://github.com/rspec/rspec-rails

mongoid-rspec/mongoid-rspec
github https://github.com/mongoid-rspec/mongoid-rspec


### 記事
Better Specs (良い例/悪い例まとめ)
http://betterspecs.org/jp/

Factory Girl Railsのチートシート - Rails Webook 
http://ruby-rails.hatenadiary.com/entry/20150102/1420174315

RailsでCucumberとRSpecを使ってテストを行う例 - Rails Webook
http://ruby-rails.hatenadiary.com/entry/20140719/1405777918

Ruby - RSpecのテンプレート - Qiita
http://qiita.com/kbaba1001/items/d01fb2b499d3122ea45e