Rspecのことが分からなさすぎたので、調べたことを後で思い出せるように備忘録をつけました。
##Rspec##
RspecはRuby言語をベースにした「ドメイン特化言語 (Domain Specific Language:DSL)」。ドメイン特化型言語 とは、特定の問題領域 (ドメイン) を記述するために設計された「言語」です。RSpecが特化しているドメインは「開発対象のプログラムの振舞を記述する」という領域。
##『TDD』 の進め方と原則##
TDD の進め方はいたってシンプルに3つ。
- プロダクトコードを書く前にテストコードを書き、それが失敗することを確認する (レッド)
- テストに成功するようにプロダクトコードを書く (グリーン)
- プログラムの振る舞いを変えないように、プロダクトコードの重複などを整理する (リファクタリング)
TDDの原則もシンプルに3つ。
- テストに失敗しない限り、プロダクトコードを書いてはいけない。
- プロダクトコードはテストを通るように書く
- テストは少しずつ書き進めていく
##ソフトウェア##
厳密にいえば、ソースコード、すなわちソフトウェア設計は次の2つの要素から構成される。
■テストコード: プログラムがどのように振る舞うべきかを定義したもの
■プロダクトコード: テストコードで定義された振る舞いを実装したもの
RSpecでは、TDDの文脈で開発を駆動する「テスト」をソフトウェア設計の要素であると位置付けている。
そしてRspecの狙いは「プログラマにテストコードが設計であることを明確に意識させることと、プログラマがテストコードをスムースに記述し実行できるようになること」。
そのために、RSpec は振舞定義用の DSL を提供し、様々なテスト関連ライブラリや周辺ツールとの連携を積極的に行い、「統合テスティング環境」となることを目指している。
##コントローラーのテスト例##
まずは、「assings」を使いたいから、下記を実行
gem 'rails-controller-testing'
###そもそもコントローラーに書く必要があるテストは何?###
コントローラーのテストで記述する必要があるのは基本的には下記の3つ。
①受信したリクエストに対して適切なレスポンスを返す
リクエストに対してHTTPレスポンスがステータスコード200を返しているかどうか?
②ビューで使用するのに必要なモデルオブジェクト(インスタンス)をロードする
リクエストされたURLから必要なモデルインスタンスをロードしているかどうか?
適切にインスタンス変数を取り出せているかどうか?
③レスポンスを表示するのに適切なビューを選択する
適切なテンプレートのビューを表示しているかどうか?
##indexアクション##
※参照URL:https://morizyun.github.io/ruby/rspec-rails-controller.html
describe 'GET #index' do
let(:articles) {create_list(:article,2)}
before {get: index, params:{}, session:{}}
① it 'has a 200 status code' do
expect(assings(:articles)).to have_http_status(:ok)
end
② it 'assigns @articles' do
expect(assigns(:articles)).to match_array articles
end
③ it 'render the :index template' do
expect(response).to render_template :index
end
end
###describe###
→describeにはテスト対象を書く。今回の場合は'GET #index'
###インスタンス変数の代わりの「let」###
上記の例でいうと、
let(:articles) {create_list(:article,2)}
のように書くと、 {create_list(:article,2)} の中の値を「articles」として参照して取り出すことができる。
ちなみに {create_list(:articles,2)}はarticlesというインスタンスを2回生成するという意味で、第二引数に繰り返す回数を数字で書く。
https://qiita.com/kindai_dai/items/617fd24b978f1ac1135f
###before###
example の前処理/ 後処理を記述するためのフック。Railsでいうところの、「before_action」のイメージ
上記の例でいうと、①〜③の検証をする前に {get: index, params:{}, session:{}}を実行している。
{①get: index, ②params:{}, ③session:{}} について
※参照:https://qiita.com/kuboon/items/8a7821e06094706d121c
①get :index 第一引数はアクション名であり、実際の url とはまったく関係がない。
②get :show, id: 3 のようにして、 params[:id] に値を直接渡すことができる。
③post :create, session: {user_id: 5}のようにして、 session: {user_id:} に値を直接渡すことができる。
セッションについて https://www.sejuku.net/blog/33677
###assignsメソッド###
→assignsメソッドはコントローラーのインスタンス変数をテストするメソッド。引数にインスタンス変数をシンボル型で渡す。
###matchマッチャ/match_arrayマッチャ###
→文字列・ハッシュ・配列の検証に用いるマッチャ。
matchマッチャで配列を検証すると、配列の順番も期待値(expect)と一致している必要がある。
一方、match_arrayマッチャで検証した場合は、要素の個数と各要素の同値性だけを検証し、順番は検証しない。
##コントローラのテストで利用できる機能(一例)##
■get(action, params={})
指定したアクションへ GET リクエストを送信。
■response()
レスポンスオブジェクトへのアクセサです。レスポンスオブジェクトは HTTP ステータスコードや使用したテンプレートの情報を保持。
■assigns[variable_name]
コントローラのインスタンス変数 @variable_name に代入されたオブジェクトを取得します。キーには “@” を除いたものを文字列またはシンボルで指定。
■post(action, params={})
指定したアクションへ POST リクエストを送信します。パラメータの指定方法は get と同じです。同様に、put や delete もある。
■session()
セッションオブジェクトへのアクセサです。セッション変数の設定やセッション ID を取得する際に利用。
■request()
リクエストオブジェクトへのアクセサです。リクエストの MIME-Type を設定する場合などに利用。
##createアクション##
この記事が分かりやすい
URL:https://forest-valley17.hatenablog.com/entry/2018/09/29/174446
※show/edit/newは下記URLを参照。
URL:https://morizyun.github.io/ruby/rspec-rails-controller.html
createアクションで、if @変数.save のように条件分岐をしている場合は、
下記のようにcontextで条件別にグループ化する。
describe 'Post #create' do
context "@articleが保存できた時" do
it "データベースに値が保存される" do
end
it "正しいビューに変遷する" do
end
end
context "@articleが保存できなかった時" do
it "データベースに値が保存されない" do
end
it "正しいビューに変遷する" do
end
end
end
以下、記述例
describe "#create" do
context "as an authenticated user" do
before do
@user = FactoryBot.create(:user)
end
it "adds a project" do
project_params = FactoryBot.attributes_for(:project)
sign_in @user
expect do
post :create, params: {project: project_params}
end.to change(@user.projects, :count).by(1)
end
end
context "as a guest" do
it "returns a 302 response" do
project_params = FactoryBot.attributes_for(:project)
post :create, params: {project: project_params}
expect(response).to have_http_status "302"
end
it "redirects to the sign-in page" do
project_params = FactoryBot.attributes_for(:project)
post :create, params: {project: project_params}
expect(response).to redirect_to "/users/sign_in"
end
end
end
sign_inを使用して、ログイン状態をシミュレートしてテストをする場合は
spec/rails_helper.rbに下記記述を追加する必要がある。
config.include Devise::Test::ControllerHelpers, type: :controller
###change + from / to / by###
例文
# popメソッドを呼ぶと配列の要素が減少することをテストする
x = [1, 2, 3]
expect(x.size).to eq 3
x.pop
expect(x.size).to eq 2
changeで置き換えると、、、
x = [1, 2, 3]
expect { x.pop }.to change{ x.size }.from(3).to(2)
読み方は下記の通り
expect{ X }.to change{ Y }.from(A).to(B) = 「X すると Y が A から B に変わることを期待する」
from().to()は「by」で書き換えることもできる、、、
x = [1, 2, 3]
expect { x.pop }.to change { x.size }.by(-1)
x = [1, 2, 3]
expect { x.push(10) }.to change { x.size }.by(1)
配列要素の個数はsizeじゃなくて、countも使える
class User < ActiveRecord::Base
# dependent: :destroy を付けたので、userを削除するとblogも削除される
has_many :blogs, dependent: :destroy
end
class Blog < ActiveRecord::Base
belongs_to :user
end
it 'userを削除すると、userが書いたblogも削除されること' do
user = User.create(name: 'Tom', email: 'tom@example.com')
# user が blog を書いたことにする
user.blogs.create(title: 'Rspec必勝法', content: 'あとで書く')
expect {user.destroy}.to change { Blog.count}.by(-1)
end
このように、change マッチャを使うと、「Xに対するある操作が、一見無関係なYに影響を与える」といった検証内容を簡潔に表現することができる。
##モック(Mock)##
何らかの理由で本物のプログラムが使えない、もしくは使わない方がよいケースでモックが使われる。
使い道としては、外部のAPIを利用しなければならない場合など。
例えば、TwitterのAPI(gem)経由でツイートするプログラムをテストする場合は、テストコードの内容がそのまま
全世界にツイートされてしまう。それは嫌だ。
ということで、モックを使えば、Twitterと同じ機能を持ち且つテストコードでのみ起動するプログラムを作ることができる。
以下、簡単な例
# 注:本当に動かす場合はtwitter gemが必要です
require 'twitter'
class WeatherBot
def tweet_forecast
twitter_client.update '今日は晴れです'
end
def twitter_client
Twitter::REST::Client.new
end
end
it 'エラーなく予報をツイートすること' do
# Twitter clientのモックを作る(doubleで作れる。)
twitter_client_mock = double('Twitter client')
# updateメソッドが呼びだせるようにする
allow(twitter_client_mock).to receive(:update)
# WeatherBotクラスのオブジェクトを作成
weather_bot = WeatherBot.new
# twitter_clientメソッドが呼ばれたら上で作ったモックを返すように実装を書き換える
allow(weather_bot).to receive(:twitter_client).and_return(twitter_client_mock)
expect{ weather_bot.tweet_forecast }.not_to raise_error
end
そのほか、詳細は下記参照
※参考ページ※ これらを読んだら基本的なRspecのことは分かります。
https://morizyun.github.io/ruby/rspec-rails-controller.html
https://qiita.com/jnchito/items/42193d066bd61c740612
https://qiita.com/jnchito/items/640f17e124ab263a54dd
https://qiita.com/jnchito/items/a4a51852c2c678b57868
https://magazine.rubyist.net/articles/0021/0021-Rspec.html
https://qiita.com/namitop/items/bf455f8383181ff6edf3
https://magazine.rubyist.net/articles/0023/0023-Rspec.html#%E6%B0%B4%E5%B9%B3%E5%88%86%E5%89%B2%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81%E3%81%A8%E5%9E%82%E7%9B%B4%E5%88%86%E5%89%B2%E3%82%A2%E3%83%97%E3%83%AD%E3%83%BC%E3%83%81
https://forest-valley17.hatenablog.com/entry/2018/09/29/174446
https://qiita.com/morrr/items/f1d3ac46b029ccddd017
https://qiita.com/muran001/items/436fd07eba1db18ed622