ruby [rspec]“使用RSpec测试Rails程序”笔记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ruby [rspec]“使用RSpec测试Rails程序”笔记相关的知识,希望对你有一定的参考价值。

# model
class Contact < ActiveRecord::Base
  has_many :phones
  accepts_nested_attributes_for :phones
  // ...
end

# controller
def contact_params
  params.require(:contact).permit(:firstname, :lastname, :email,
    :phones_attributes => [:id, :phone, :phone_type])
end
def create
  @contact = Contact.new(contact_params)
end

# view
<%= form_for(@contact) do |f| %>
  <div class="form-inputs">
    <div class="field">
      <%= f.label :firstname %>
      <%= f.text_field :firstname %>
    </div>
    <div class="field">
      <%= f.label :lastname %>
      <%= f.text_field :lastname %>
    </div>
    <div class="field">
      <%= f.label :email %>
      <%= f.text_field :email %>
    </div>
    <%= f.fields_for :phones do |builder| %>
      <div class="field">
        <%= builder.hidden_field :phone_type %>
        <%= builder.label :phone, builder.object.phone_type %>
        <%= builder.text_field :phone %>
      </div>
    <% end %>
  </div>
  <div class="form-actions">
    <%= f.submit nil, class: 'btn btn-primary' %>
  </div>
<% end %>
# scenario 和 feature 只能用于功能测试
# feature 块不能嵌套 descibe

# feature instead describe
feature 'User management' do
  // scenario instead it
  scenario "adds a new user" do
    admin = create(:admin)
    sign_in admin

    visit root_path
    expect{
      click_link 'Users'
      click_link 'New User'
      fill_in 'Email', with: 'newuser@example.com'
      find('#password').fill_in 'Password', with: 'secret123'
      find('#password_confirmation').fill_in 'Password confirmation',
        with: 'secret123'
      click_button 'Create User'
    }.to change(User, :count).by(1)
    
    // gem launcher: 可以保存当前页面并打开
    save_and_open_page
    
    expect(current_path).to eq users_path
    expect(page).to have_content 'New user created'
    within 'h1' do
      expect(page).to have_content 'Users'
    end
    expect(page).to have_content 'newuser@example.com'
  end
end
# 控制器动作实例化的变量可以通过 assigns(:variable_name) 方法获取
it "assigns the requested contact to @contact" do
  contact = create(:contact)
  get :show, id: contact
  expect(assigns(:contact)).to eq contact
end

# 控制器动作的返回结果可以通过 response 获取
it "renders the :show template" do
  contact = create(:contact)
  get :show, id: contact
  expect(response).to render_template :show
end

# attribute_for() 方法生成的是一个由属性组成的Hash,而不是对象
it "redirects to the home page upon save" do
  post :create, contact: FactoryGirl.attributes_for(:contact)
  expect(response).to redirect_to root_url
end

# 提供的match_array 匹配器检测数据集合(被赋值给@contacts 变量)是否和期望值
# 一致。match_array 只会检测数组的内容,而不在乎顺序。如果还要检测顺序的话,请使
# 用eq 匹配器。
it "populates an array of contacts starting with the letter" do
  smith = create(:contact, lastname: 'Smith')
  jones = create(:contact, lastname: 'Jones')
  get :index, letter: 'S'
  expect(assigns(:contacts)).to match_array([smith])
end

# be_a_new
it "assigns a new Contact to @contact" do
  get :new
  expect(assigns(:contact)).to be_a_new(Contact)
end

# expect a post and change or not
# 注意第一个测试用例中 expect 方法和之前不一样的用法。这里,我们把整个 HTTP
# 请求操作包含在了 expect{} 块中,这种用法比较复杂。块中的 HTTP 请求操作以 Proc 的
# 形式传入 expect 方法,这样要检测的值会在请求执行前后两次计算,能很方便的检测预想
# 的变化有没有发生。
it "saves the new contact in the database" do
  expect{
    post :create, contact: attributes_for(:contact,
      phones_attributes: @phones)
  }.to change(Contact, :count).by(1)
end
it "does not save the new contact in the database" do
  expect{
    post :create,
      contact: attributes_for(:invalid_contact)
  }.to_not change(Contact, :count)
end


# attributes_for(:contact, phones_attributes: @phones) 可以构成nested_form
before :each do
  @phones = [
    attributes_for(:phone),
    attributes_for(:phone),
    attributes_for(:phone)
  ]
end
post :create, contact: attributes_for(:contact, phones_attributes: @phones)

# reload
it "changes @contact's attributes" do
  patch :update, id: @contact,
    contact: attributes_for(:contact,
      firstname: "Larry", lastname: "Smith")
  @contact.reload
  expect(@contact.firstname).to eq("Larry")
  expect(@contact.lastname).to eq("Smith")
end

# should的用法
subject { build(:user, firstname: 'John', lastname: 'Doe') }
it { should be_named 'John Doe' }
# Shoulda 是一个匹配器扩展代码库,可以更轻松地测试常规功能。添加这样一个额外的依
# 赖库,就可以把之前要使用三行四行甚至五行的测试精简到只需一到两行。

subject{ Contact.new }
specify { should validate_presence_of :firstname }

subject { build(:user, firstname: 'John', lastname: 'Doe') }
it { should be_named 'John Doe' }
RSpec::Matchers.define :be_named do |expected|
  match do |actual|
    actual.name eq expected
  end
  description do
    "return a full name as a string"
  end
end
FactoryGirl.define do
  factory :contact do
    firstname { Faker::Name.first_name }
    lastname { Faker::Name.last_name }
    email { Faker::Internet.email }
    
    after(:build) do |contact|
      [:home_phone, :work_phone, :mobile_phone].each do |phone|
        contact.phones << FactoryGirl.build(:phone,
        phone_type: phone, contact: contact)
      end
   end
end

FactoryGirl.define do
  factory :phone do
    association :contact
    phone { Faker::PhoneNumber.phone_number }
  
    # child factories omitted ...
  end
end
# 手动引入urlhelper
redirect_to Rails.application.routes.url_helpers.login_path

it "processes a credit card", focus: true do
# details of example
end
# $ bundle exec rspec . --tag focus
# 还可以设置 RSpec 只运行(或不运行)指定标签的测试用例,例如:
# spec/spec_helper.rb
Spork.prefork do
  # ...
  RSpec.configure do |config|
    # ...
    config.filter_run focus: true
  end
end
# 所有属性都自动会有个带?的方法
@contact.hidden?
@contact.lastname?
@contact.created_at?

以上是关于ruby [rspec]“使用RSpec测试Rails程序”笔记的主要内容,如果未能解决你的问题,请参考以下文章

ruby Rspec+jenkins+allure持续集成

Rspec 挂在 Ruby 2.7.4 上,没有输出

ruby 为Rspec添加Devise支持

ruby 沉默RSpec规格

ruby 用Rspec测试模型

ruby rspec_retry.rb