I'm studying rails and rspec.
And I made rspec unit test (request test) on rails application.
But after searching on google, I'm wonder if my job is on right way.
Can my code be a "Unit test by function(not a method, web site's feature ex)create, show, delete..) of rails application" ?
this is my code with request test.
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:users) { create_list(:user, 10) }
let(:user_id) { users.first.id }
let(:user) { create(:user) }
def send_request_to_store_user(name, mailaddress)
post '/users', params: {
user: {
name: users.first.name,
mailaddress: users.first.mailaddress
}
}
end
def http_status_success_and_body_element_check(body_element)
expect(response).to have_http_status(:success)
expect(response.body).to include(body_element)
end
describe 'GET' do
context 'Get /users test' do
it 'test user list page' do
get '/users'
http_status_success_and_body_element_check('User List')
end
end
context 'Get /users/create test' do
it 'test user create page' do
get '/users/create'
http_status_success_and_body_element_check('create user')
end
end
context 'Get /users/:id/edit' do
it 'test user edit page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('edit user')
end
end
context 'Get /users/:id' do
it 'test user show page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('show user')
end
end
end
describe 'POST' do
context 'test store new user' do
it 'test create new user' do
send_request_to_store_user(user.name, user.mailaddress)
expect do
create(:user)
end.to change { User.count }.from(User.count).to(User.count + 1)
end
it 'test redirect after create' do
send_request_to_store_user(user.name, user.mailaddress)
expect(response).to have_http_status(302)
end
end
end
describe 'DELETE' do
it 'test delete user' do
expect do
delete "/users/#{user_id}"
end.to change { User.count }.from(User.count).to(User.count - 1)
expect(response).to have_http_status(302)
end
end
describe 'PUT' do
context 'user update' do
it 'test user information update' do
old_name = users.first.name
new_name = 'new_name'
expect do
put "/users/#{user_id}", params: {
user: {
name: new_name
}
}
end.to change { users.first.reload.name }.from(old_name).to(new_name)
expect(response).to have_http_status(:redirect)
end
end
end
end
this is my code with test on model
require 'rails_helper'
RSpec.describe User, type: :model do
it 'user must have name and mailaddress' do
user = create(:user)
expect(user).to be_valid
expect(user.name).not_to be_nil
expect(user.mailaddress).not_to be_nil
end
it 'mailaddress must include #' do
# user = FactoryBot.create(:user)
# If rails_helper.rb has config.include FactoryBot::Syntax::Methods,
# Can use shortcut. Don't have to FactoryBot.create
user = create(:user)
# Test pass if email match with regexp
expect(user.mailaddress).to match(/\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/)
end
end
I don't think these tests are valuable (meaningful).
Here's my reasoning:
What are these tests telling you? That the Rails router is working? That the controller is responding with the right action? Neither of these are your responsibility to test. Rails has that covered.
If you want to know "does the index page render?" and "can I CRUD a user?" then write system tests with Capybara that simulate the whole flow. That way you are testing the real-world interaction with your whole system.
I am testing the avaibility of BusinessArea views in the context of signed in / not signed in user.
At the beginning of the test, I create the business area object (test_ba) thanks to the factory, which returns the object.
I 'puts' the test_ba.id to see it created.
Then I request the tested view.
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
test_ba = FactoryBot.create(:business_area)
puts test_ba.id
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
The test seems to run correctly, but the last step fails due to missing record!?! The ouput returns:
>rspec spec/requests/business_areas_spec.rb
67
.67
..67
F
Failures:
1) BusinessArea Business Areas pages: when signed in should display business area
Failure/Error: #business_area = BusinessArea.find(params[:id])
ActiveRecord::RecordNotFound:
Couldn't find BusinessArea with 'id'=67
# ./app/controllers/business_areas_controller.rb:159:in `set_business_area'
# ./spec/requests/business_areas_spec.rb:35:in `block (4 levels) in <top (required)>'
Finished in 2.07 seconds (files took 13.05 seconds to load)
4 examples, 1 failure
Failed examples:
rspec ./spec/requests/business_areas_spec.rb:33 # BusinessArea Business Areas pages: when signed in should display business area
Can you help me find what's wrong with this?
RSpec has the let and let! methods that create memoized helpers that you should use to setup your test dependency. let is lazy loading (the block is not evaluated until you reference it) while let! is not.
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
let!(:test_ba){ FactoryBot.create(:business_area) }
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
But wah! Why doesn't my code work?
In RSpec (and in any good test framework) each example runs in isolation and has its own setup and teardown. This includes rolling back the database or clearing it. RSpec does not even run the tests in consecutive order by design.
The record you are defining in the outer context will not be created for each test run. After the first example when the db is is rolled back its gone.
If you want to set something up for each test use before:
require 'rails_helper'
RSpec.describe BusinessArea, type: :request do
include Warden::Test::Helpers
describe "Business Areas pages: " do
before do
#test_ba = FactoryBot.create(:user)
end
context "when not signed in " do
it "should propose to log in when requesting index" do
get business_areas_path
follow_redirect!
expect(response.body).to include('Sign in')
end
it "should propose to log in when requesting show" do
puts #test_ba.id
get business_area_path(test_ba)
follow_redirect!
expect(response.body).to include('Sign in')
end
end
context "when signed in" do
before do
get "/users/sign_in"
#test_user = FactoryBot.create(:user)
login_as test_user, scope: :user
end
it "should display index" do
get business_areas_path
expect(response).to render_template(:index)
end
it "should display business area" do
puts #test_ba.id
get business_area_path(test_ba)
expect(response).to render_template(:show)
end
end
end
end
But let / let! are preferred for setting up simple dependencies.
This below is spec/controllers/askings_controller_spec.rb.
require 'rails_helper'
RSpec.describe AskingsController, type: :controller do
describe 'Get #show' do
before do
#asking=create(:asking)
get :show , id: #asking.id
end
it 'should render show' do
expect(response).to render_template(:show)
end
it 'should Undertaking new' do
expect(assigns(:undertaking)).to be_a_new(Undertaking)
end
it 'should response status 200' do
expect(response.status).to eq(200)
end
end
end
And rspec result is below.
1) AskingsController Get #show should render show
Failure/Error: #asking=Asking.find(id: params[:id])
ActiveRecord::RecordNotFound:
Couldn't find Asking with 'id'={:id=>"2"}
2) AskingsController Get #show should Undertaking new
Failure/Error: #asking=Asking.find(id: params[:id])
ActiveRecord::RecordNotFound:
Couldn't find Asking with 'id'={:id=>"2"}
3) AskingsController Get #show should response status 200
Failure/Error: #asking=create(:asking)
ActiveRecord::RecordInvalid:
mailaddress exists.(<- I translate Japanese error.)
Why do I have this eeror?
I think that how to use 'create' is incorrect.
Please tell me the solution.
I tried to write some tests for the "show" action in Rails API
require 'rails_helper'
RSpec.describe AirlinesController, type: :controller do
describe "GET #show" do
before(:each) do
#airline = FactoryGirl.create(:airline)
get :show, id: #airline.id
end
it "should return the airline information" do
airline_response = json_response
expect(airline_response[:name]).to eql #airline.name
end
it {should respond_with :ok}
end
end
The test passed. However, when I try to use let and subject like this
require 'rails_helper'
RSpec.describe AirlinesController, type: :controller do
describe "GET #show" do
let(:airline) {FactoryGirl.create(:airline)}
subject {airline}
before(:each) do
get :show, id: airline.id
end
it "should return the airline information" do
airline_response = json_response
expect(airline_response[:name]).to eql airline.name
end
it {should respond_with :ok}
end
end
It showed "NoMethodError undefined method `response' for ..."
This makes me confused!
Don't set the subject. The subject of a controller spec is the controller, not a model object. Just remove the line that sets subject and you shouldn't get that error any more.
it {should respond_with :ok}
I assume this line takes the subject and makes a response call.
The recommended syntax is:
it "returns 200" do
expect(response).to be_success
end
Or maybe your json_response helper method is using subject.response instead of response.
As I added tests to my spec, I became suspicious about every single one passing just as I wrote it, so I added some syntax errors into the code. It turned out, Rspec was not running my recent tests so the syntax errors weren't getting picked up. The comment in the code below shows the arbitrary line at which Rspec stopped showing a green period or red F for a test:
require 'spec_helper'
describe Api::PagesController do
def valid_session
{}
end
describe "GET index" do
before :each do
#page = create(:page)
end
it "assigns all pages as #pages" do
get :index
assigns(:pages).should eq([#page])
end
it "returns json" do
get :index, format: :json
expect(response.body).to have_content #page.to_json
end
end
describe "GET show" do
before :each do
#page = create(:page)
end
it "assigns the requested page as #page" do
get :show, {:id => #page.to_param}, valid_session
assigns(:page).should eq(#page)
end
it "returns json" do
get :show, {:id => #page.to_param}, valid_session, format: :json
expect(response.body).to have_content #page.to_json
end
end
describe "GET new" do
it "assigns a new page as #page" do
get :new, {}, valid_session
assigns(:page).should be_a_new(Page)
end
end
describe "GET edit" do
before :each do
#page = create(:page)
end
it "assigns the requested page as #page" do
get :edit, {:id => #page.to_param}, valid_session
assigns(:page).should eq(#page)
end
it "returns json" do
get :edit, {:id => #page.to_param}, valid_session, format: :json
expect(response.body).to have_content #page.to_json
end
end
describe "POST create" do
describe "with valid params" do
it "creates a new Page in the database" do
expect {
post :create, {page: attributes_for(:page)}, valid_session
}.to change(Page, :count).by(1)
end
it "assigns a newly created page as #page" do
post :create, {page: attributes_for(:page)}, valid_session
assigns(:page).should be_a(Page)
assigns(:page).should be_persisted
end
#################### TESTS BELOW HERE ARE NOT SHOWN BY RSPEC ##########
it "returns json" do
expect{
post :create, {page: attributes_for(:page)}, valid_session
}.to have_content page.to_json
end
end
describe "with invalid params" do
it "does not save the new page in the database" do
expect {
post :create, {page: attributes_for(:page_invalid)}, valid_session
}.to_not change(Page, :count).by(1)
end
#FUTURE TEST - redirects to new page with errors
end
end
describe "PUT update" do
before :each do
#page = create(:page)
end
describe "with valid params" do
it "updates the requested page" do
Page.any_instance.should_receive(:update_attributes).with({ "title" => "MyString" })
put :update, {:id => #page.to_param, :page => { "title" => "MyString" }}, valid_session
end
it "assigns the requested page as #page" do
put :update, {:id => #page.to_param, :page => { "title" => "MyString" }}, valid_session
assigns(:page).should eq(#page)
end
it "returns json" do
put :update, {:id => #page.to_param, :page => { "title" => "MyString" }}, valid_session, format: :jason
expect(response.body).to have_content #page.to_json
end
end
describe "with invalid params" do
it "assigns the page as #page" do
# Trigger the behavior that occurs when invalid params are submitted
Page.any_instance.stub(:save).and_return(false)
put :update, {:id => #page.to_param, page: attributes_for(:page_invalid)}, valid_session
assigns(:page).should eq(#page)
end
#FUTURE TEST - redirects to edit
end
end
describe "DELETE destroy" do
before :each do
#page = create(:page)
end
it "destroys the requested page" do
expect {
delete :destroy, {:id => #page.to_param}, valid_session
}.to change(Page, :count).by(-1)
end
end
describe "GET published" do
before :each do
#page = create(:page)
#page_unpublished = create(:page_unpublished)
end
it "returns a list of published pages only" do
get :published, format: :json
assigns(:pages).should eq([#page])
expect(response.body).to have_content #page_unpublished.to_json
end
end
describe "GET unpublished" do
before :each do
#page = create(:page)
#page_unpublished = create(:page_unpublished)
end
it "returns a list of unpublished pages only" do
get :unpublished, format: :json
assigns(:pages).should eq([#page_unpublished])
expect(response.body).to have_content #page_unpublished.to_json
end
end
describe "GET total_words" do
before :each do
#page = create(:page)
end
it "returns a list of unpublished pages only" do
get :total_words, {:id => #page.to_param}, format: :json
assigns(:total_words).should eq([#page_unpublished])
expect(response.body).to have_content #page_unpublished.to_json
end
end
end
When I intentionally added syntax errors to earlier tests, I saw red Fs instead of the green dots, but no error was being reported. Earlier, when there was an error, I'd see (for example):
16:10:14 - INFO - Running: spec/models/page_spec.rb
Running tests with args ["--drb", "-f", "progress", "-r", "/Users/user/.rvm/gems/ruby-1.9.3-p194/gems/guard-rspec-1.2.1/lib/guard/rspec/formatters/notification_rspec.rb", "-f", "Guard::RSpec::Formatter::NotificationRSpec", "--out", "/dev/null", "--failure-exit-code", "2", "spec/models/page_spec.rb"]...
.......F
Failures:
1) Page Publishing returns unpublished pages
Failure/Error: expect(Page.unpublished).to eq [page_unpublished]
NameError:
undefined local variable or method `page_unpublished' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_4:0x0000010355b8c0>
# ./spec/models/page_spec.rb:58:in `block (3 levels) in <top (required)>'
Finished in 0.54212 seconds
8 examples, 1 failure
Failed examples:
rspec ./spec/models/page_spec.rb:57 # Page Publishing returns unpublished pages
Done.
Now, when a test fails, I only see:
17:55:26 - INFO - Running: spec/controllers/pages_controller_spec.rb
Running tests with args ["--drb", "-f", "progress", "-r", "/Users/user/.rvm/gems/ruby-1.9.3-p194/gems/guard-rspec-1.2.1/lib/guard/rspec/formatters/notification_rspec.rb", "-f", "Guard::RSpec::Formatter::NotificationRSpec", "--out", "/dev/null", "--failure-exit-code", "2", "spec/controllers/pages_controller_spec.rb"]...
.....FF..Done.
This change happened at a pretty arbitrary time while coding, I didn't modify the Guardfile or any Spork configuration details. Any idea why this is happening?
It looks like one of the arguments to rspec is --out /dev/null. So your output is being redirected from STDOUT.
One possibility is that this is being caused by one or more bad tests. Have you tried commenting out various tests to see if you can isolate the problematic ones? If you're running your tests in order, I'd start with the ones just before and after the point at which rspec exits prematurely.