I have a page where a logged in user can set a favourite item by submitting an ajax request which calls the following controller method:
def create
item = Item.find(params[:id])
if params[:commit] == 'save'
item.unhide!(current_user) if item.is_hidden_by?(current_user)
item.save_as_favourite_for!(current_user)
else
item.remove_as_favourite!(current_user) if item.is_favourite_of?(current_user)
item.hide_for!(current_user)
end
respond_to do |format|
format.html { redirect_back_or items_path }
format.js {render "create", locals: {item: item, index: params[:index].to_i || nil, page:params[:page]} }
end
end
This all works as expected, but I have a request spec which is defined to test the front end functionality:
describe "saving items", js: true do
let!(:current_user) {FactoryGirl.create(:user, email: "user#example.com", active: true)}
let!(:favourite_item) {FactoryGirl.create(:item)}
before { sign_in current_user }
describe "when saving a item" do
it "should save the user against the item" do
expect do
click_button "save"
# sleep(0.02)
end.to change(favourite_item.savers, :count).by(1)
end
end
end
Notice the commented out sleep(0.02) statement. If this statement is commented out, the test (almost) always fails, however if I include the call, and immediately end the console interrupt, then the test passes every time. (any more time delay and it fails).
Is there a better way to write this test without the workaround and why might it be failing without the delay?
You can set a wait time using the dsl.
using_wait_time 3 do
expect do
click_button "save"
end.to change(favourite_item.savers, :count).by(1)
end
http://rubydoc.info/github/jnicklas/capybara/Capybara.using_wait_time
The default wait time is 2 seconds for JS tests if you use an Rspec matcher, which you are not for this test.
It'll probably also pass if you remove js: true unless you really need the js driver for the test.
Related
Here is my controller spec
before do
#order = Order.new
end
it "should call find & assign_attributes & test delivery_start methods" do
Order.should_receive(:find).with("1").and_return(#order)
Order.any_instance.should_receive(:assign_attributes).with({"id"=>"1", "cancel_reason" => "random"}).and_return(#order)
Order.any_instance.should_receive(:delivery_start).and_return(Time.now)
post :cancel, order: {id:1, cancel_reason:"random"}
end
The failure is this:
Failure/Error: Unable to find matching line from backtrace
(#<Order:0x007fdcb03836e8>).delivery_start(any args)
expected: 1 time with any arguments
received: 2 times with any arguments
# this backtrace line is ignored
But I'm not sure why delivery_start is being called twice based on this controller action:
def cancel
#order = Order.find(cancel_params[:id])
#order.assign_attributes(cancel_params)
if (#order.delivery_start - Time.now) > 24.hours
if refund
#order.save
flash[:success] = "Your order has been successfully cancelled & refunded"
redirect_to root_path
else
flash[:danger] = "Sorry we could not process your cancellation, please try again"
render nothing: true
end
else
#order.save
flash[:success] = "Your order has been successfully cancelled"
redirect_to root_path
end
end
I would suggest you test the behavior and not the implementation. While there are cases where you would want to stub out the database doing it in a controller spec is not a great idea since you are testing the integration between your controllers and the model layer.
In addition your test is only really testing how your controller does its job - not that its actually being done.
describe SomeController, type: :controller do
let(:order){ Order.create } # use let not ivars.
describe '#cancel' do
let(:valid_params) do
{ order: {id: '123', cancel_reason: "random"} }
end
context 'when refundable' do
before { post :cancel, params }
it 'cancels the order' do
expect(order.reload.cancel_reason).to eq "random"
# although you should have a model method so you can do this:
# expect(order.cancelled?).to be_truthy
end
it 'redirects and notifies the user' do
expect(response).to redirect_to root_path
expect(flash[:success]).to eq 'Your order has been successfully cancelled & refunded'
end
end
end
end
I would suggest more expectations and returning true or false depending on your use. Consider the following changes
class SomeController < ApplicationController
def cancel
...
if refundable?
...
end
end
private
def refundable?
(#order.delivery_start - Time.now) > 24.hours
end
end
# spec/controllers/some_controller_spec.rb
describe SomeController, type: :controller do
describe '#cancel' do
context 'when refundable' do
it 'cancels and refunds order' do
order = double(:order)
params = order: {id: '123', cancel_reason: "random"}
expect(Order).to receive(:find).with('123').and_return(order)
expect(order).to receive(:assign_attributes).with(params[:order]).and_return(order)
expect(controller).to receive(:refundable?).and_return(true)
expect(controller).to receive(:refund).and_return(true)
expect(order).to receive(:save).and_return(true)
post :cancel, params
expect(response).to redirect_to '/your_root_path'
expect(session[:flash]['flashes']).to eq({'success'=>'Your order has been successfully cancelled & refunded'})
expect(assigns(:order)).to eq order
end
end
end
end
Sorry, this is a very unsatisfactory answer, but I restarted my computer and the spec passed...
One thing that has been a nuisance for me before is that I've forgotten to save the code, i.e., the old version of the code the test is running against called delivery_start twice. But in this case, I definitely checked that I had saved. I have no idea why a restart fixed it...
My app's view specs started failing because of a change we introduced to version control and I'm trying to debug.
Essentially, we had a ton of view specs that always passed, and now many, but not all, of them produce ActionController::UnknownFormat errors in the controller. Here is an example of a method that is blowing up:
def show
#user = current_user
FooCountItem.increment_requests
respond_to do |format|
format.html
format.json { render json: #foo }
end
end
Here's an example of spec that just began to fail:
describe "show.html.erb", type: :feature do
context "as admin" do
let!(:foo) { FactoryGirl.create(:foo) }
let!(:user) { FactoryGirl.create(:admin_user) }
before :each do
page.set_rack_session(user_id: user.id)
visit data_service_path(foo.id)
end
it "displays the foo's name" do
expect(page).to have_content(foo.name)
end
end
end
How is it possible that html is not the expected response? How could the controller react as if it has to produce a different format?
Thanks.
Updated: I'm also seeing NoMethodError: undefined method 'symbolize_keys' for "":String on some lines that are doing the controller requests from the controller_specs. I have no changes in spec_helper.rb when compared to working version of the test suite.
If I add format: :html to a call in the controller spec, it passes. I must have the app globally configured to respond to :js.
I have a method in my controller, unreserve, that calls the find_ticket method and, if the ticket is found, calls unreserve on the ticket.
def unreserve
if find_ticket
#ticket.unreserve current_or_guest_user
render json: #ticket.show.tickets_as_json(current_or_guest_user)
else
render json: nil
end
end
private
def find_ticket
#ticket = Ticket.find params[:id]
rescue ActiveRecord::RecordNotFound
false
else
true
end
Since I have already tested unreserve, I just want to make sure that method is called. So when I try running this spec:
describe 'unreserving a ticket' do
before :each do
sign_in customer
put :reserve, id: ticket_1.id
put :unreserve, id: ticket_1.id
end
it 'calls unreserve on the ticket with the current user' do
expect(controller.instance_variable_get :#ticket).to receive(:unreserve)
end
end
Which I would expect to pass, I even threw a puts statement inside the unreserve method to make sure that method was being called, and it outputted to the console when I was running the test.
The output I get from the test is:
1) TicketsController unreserving a ticket calls unreserve on the ticket with the current user
Failure/Error: expect(controller.instance_variable_get :#ticket).to receive(:unreserve)
(#<Ticket:0x007f90dd6c9600>).unreserve(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
EDIT:
I tried calling the expect statement before the actions, but still got the same error. Here is the new test:
it 'calls unreserve on the ticket with the current user' do
expect(controller.instance_variable_get :#ticket).to receive(:unreserve)
sign_in customer
put :reserve, id: ticket_1.id
put :unreserve, id: ticket_1.id
end
Unless I'm remembering incorrectly you need to make your expectations before you make your request. Meaning you can't use a before block (well not entirely). Move your put :unreserve down after your expect call like so:
describe 'unreserving a ticket' do
before :each do
sign_in customer
put :reserve, id: ticket_1.id
end
it 'calls unreserve on the ticket with the current user' do
expect(controller.instance_variable_get :#ticket).to receive(:unreserve)
put :unreserve, id: ticket_1.id
end
end
I'm trying to run a Capybara 1.0 test on my Rails 3 app to test whether when a user clicks on a confirmation link, he is actually confirmed.
Now, this actually works when I test it manually. In addition, as you can see there is a puts #user.confirmed line that I put in the confirm method to debug this, and it actually prints true when I run the test. However, the test itself fails.
It seems as if the confirmed attribute in my user model isn't being remembered by the test after executing the controller method.
What am I missing? Thanks so much in advance.
Test:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user.confirmed.should be_true
end
Controller method:
def confirm
#confirmation_code = params[:confirmation_code]
#user = User.find_by_confirmation_code(#confirmation_code)
#website = #user.website
#user.confirm
if #user.referrer_id
User.find(#user.referrer_id).increment_signups
end
flash[:success] = "Thanks for signing up!"
flash[:user_show] = #user.id
puts #user.confirmed
redirect_to "http://" + #website.domain_name
end
User model method:
def confirm
self.confirmed = true
self.save
end
Is it that you would need to reload the user object after visiting the confirm_path? Try this:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_true
end
Alternatively you could use #user.reload.
The user object referred to in your test is just a copy of the object being manipulated by the application so it is not going to be automatically refreshed. You need to fetch it from the database a second time to get the updated values.
I want to test that my controller action is rendering a partial.
I've poked around and I can't seem to find anything that works.
create action:
def create
#project = Project.new...
respond_to do |format|
if #project.save
format.js { render :partial => "projects/form" }
end
end
end
spec:
it "should save and render partial" do
....
#I expected/hoped this would work
response.should render_partial("projects/form")
#or even hopefully
response.should render_template("projects/form")
#no dice
end
If you're looking for a REAL answer... (i.e. entirely in RSpec and not using Capybara), the RSpec documentation says that render_template is a wrapper on assert_template. assert_template (according to the docs) also indicates that you can check that a partial was rendered by including the :partial key.
Give this a go...
it { should render_template(:partial => '_partialname') }
Update see bluefish's answer below, it seems to be the correct answer
Would you consider using Capybara for your integration testing? I found ajax difficult to test with rspec alone. In your case I'm not even sure you are getting a response back yet. In capybara it waits for the ajax call to finish and you can call the page.has_xxxx to see if it was updated. Here is an example:
it "should flash a successful message" do
visit edit_gallery_path(#gallery)
fill_in "gallery_name", :with => "testvalue"
click_button("Update")
page.has_selector?("div#flash", :text => "Gallery updated.")
page.has_content?("Gallery updated")
click_link "Sign out"
end
another great way to test your ajax controller method is to check the assignments which are later used to render the result. Here is a little example:
Controller
def do_something
#awesome_result = Awesomeness.generete(params)
end
JBuilder
json.(#awesome_result, :foo, :bar)
Rspec Controller Test
describe :do_something do
before do
#valid_params{"foo" => "bar"}
end
it "should assign awesome result" do
xhr :post, :do_something, #valid_params
assigns['awesome_result'].should_not be_nil
end
end