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...
Related
Hi guys i have a controller code with the following :
class FeedbacksController < ApplicationController
def create
#feedback = Feedback.create(feedback_params)
if #feedback.errors.any?
flash[:error] = #feedback.errors
render 'new'
else
redirect_to :back
end
end
test spec
require "rails_helper"
RSpec.describe FeedbacksController do
describe "POST create" do
context 'when param[:name] is present' do
it 'should redirect to homepage' do
#feedback = Feedback.create(:name => "Hah")
#feedback.save
is_expected.to redirect_to new_feedback_path
debugger
end
end
end
end
However when i run localhost, the output is exactly what i want but as of the unit test, it's not passing but returning me
"Expected response to be a m was was <200> ."
May i know why is it so and how should i pass my test case ?
You are missing the actual post request to your controller.
Please read this: rspec documentation
So this:
#feedback = Feedback.create(:name => "Hah")
#feedback.save
Should not be there. The second line is superfluous anyway, create already saves the object.
You are looking for code like this:
it 'should redirect to homepage' do
post feedback_path, params: { name: 'hah' }
expect(response).to redirect_to(new_feedback_path)
end
I have the following models:
subscription, user, and events
A user has_one subscription
A subscription belongs_to a user
A user has_many events
An event belongs_to a user
So far I have been able to successfully create acceptance tests using Capybara and RSpec. This allows me to 'upgrade' a users account (which adds a different role). I've also been able to do an acceptance test where the user cancels their subscription and ensure their roles are removed.
However, now I want to ensure that any of the user's open events are cancelled. This is where I'm getting stuck. Actually, I didn't even get this far because I ran in to trouble trying to even destroy a subscription.
So, I created a controller spec called subscriptions_controller_spec.rb. In this spec, there is a test to ensure the destroy action works as expected. This is failing because in my controller it goes to retrieve the customer and subscription, which doesn't exist, and returns an Stripe::InvalidRequestError.
In order to get around this, I tried to use stripe-ruby-mock to mock the stripe servers. However, I'm not sure how I'm supposed to use this in a controller spec and I got really confused. Below is my controller and my controller spec. Any advice on how I should attack this would be really appreciated.
subscriptions_controller_spec.rb
require 'rails_helper'
RSpec.describe SubscriptionsController, :type => :controller do
let(:stripe_helper) { StripeMock.create_test_helper }
before { StripeMock.start }
after { StripeMock.stop }
# ... omitted
describe 'DELETE destroy' do
before :each do
sign_in_trainer
#subscription = create(:subscription, user: subject.current_user)
plan = stripe_helper.create_plan(:id => 'Standard')
customer = Stripe::Customer.create({
email: 'johnny#appleseed.com',
source: stripe_helper.generate_card_token,
plan: 'Standard'
})
#subscription.customer_id = customer.id
#subscription.stripe_sub_id = customer.subscriptions.data.first.id
end
it 'destroys the requested subscription' do
expect {
delete :destroy, {:id => #subscription.to_param}
}.to change(Subscription, :count).by(-1)
end
# ... omitted
end
end
And subscriptions_controller.rb
class SubscriptionsController < ApplicationController
before_action :set_subscription, only: [:update, :destroy]
# ... ommitted
# DELETE /cancel-subscriptions/1
def destroy
begin
customer = Stripe::Customer.retrieve(#subscription.customer_id)
customer.subscriptions.retrieve(#subscription.stripe_sub_id).delete
rescue Stripe::CardError => e
# User's card was declined for many magnitude of reasons
redirect_to user_dashboard_path, alert: 'There was a problem cancelling your subscription' and return
rescue Stripe::APIConnectionError => e
# Stripe network issues
redirect_to user_dashboard_path, alert: 'Network issue. Please try again later' and return
rescue Stripe::APIError => e
# Stripe network issues
redirect_to user_dashboard_path, alert: 'Network issue. Please try again later' and return
rescue Stripe::InvalidRequestError => e
# This is something that we screwed up in our programming. This should literally never happen.
redirect_to user_dashboard_path, alert: 'There was a problem cancelling your subscription.' and return
rescue => e
logger.error e.message
logger.error e.backtrace.join("\n")
redirect_to user_dashboard_path, alert: 'There was a problem cancelling your subscription.' and return
end
if current_user.events
#events = current_user.events
#events.open.each do |event|
event.cancel
end
end
current_user.remove_role 'trainer'
current_user.add_role 'user'
current_user.save
#subscription.destroy
respond_to do |format|
format.html { redirect_to user_dashboard_path, notice: 'Subscription cancelled. All your open events have been cancelled.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_subscription
#subscription = Subscription.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def subscription_params
params[:subscription]
end
end
I think you've kind of already hit the nail on the head here, the fact that it's hard to test in a controller spec indicates that it might be a good time to consider moving the behaviour to a service class.
What i'd do is setup an integration test to use as your feedback loop, then refactor and get back to green. Once you've done that, start refactoring your service class and building on your specs from there.
Does simply mocking out Stripe not work eg:
require 'rails_helper'
RSpec.describe SubscriptionsController, :type => :controller do
# ... omitted
describe 'DELETE destroy' do
before :each do
sign_in_trainer
#subscription = create(:subscription, user: subject.current_user)
end
it 'destroys the requested subscription' do
# just mock stripe to pass back the customer you expect - as though it Just Works
expect(Stripe::Customer).to receive(:retreive).and_return(subscription.customer)
expect {
delete :destroy, {:id => #subscription.to_param}
}.to change(Subscription, :count).by(-1)
end
it 'does not destroy it if we got a card error' do
# likewise you can mock up what happens when an error is raised
expect(Stripe::Customer).to receive(:retreive).and_raise(Stripe::CardError)
expect {
delete :destroy, {:id => #subscription.to_param}
}.not_to change(Subscription, :count)
end
# ... omitted
end
end
I am trying to test to see if posting to a create method in my controller triggers a callback I defined with after_save
Here's the controller method being posted to
def create
#guest = Guest.new(guest_params)
#hotel = Hotel.find(visit_params[:hotel_id])
#set visit local times to UTC
#visit= Visit.new(visit_params)
#visit.checked_out_at = (DateTime.now.utc + visit_params[:checked_out_at].to_i.to_i.days).change(hour: #visit.hotel.checkout_time.hour)
#visit.checked_in_at = Time.now.utc
##visit.user_id = current_user.id
#self_serve = (params[:self_serve] && params[:self_serve] == "true")
if #guest.save
#visit.guest_id = #guest.id
if #visit.save
if #self_serve
flash[:notice] = "#{#visit.guest.name}, you have successfully checked in!."
redirect_to guest_checkin_hotel_path(#visit.hotel)
else
flash[:notice] = "You have successfully checked in #{#visit.guest.name}."
redirect_to hotel_path(#visit.hotel)
end
else
render "new"
end
else
render "new"
end
end
Here's my spec/controllers/guests_controller_spec.rb test that is failing
RSpec.describe GuestsController, :type => :controller do
describe "#create" do
let!(:params) do { name: "John Smith", mobile_number: "9095551234" } end
context "when new guest is saved" do
it "triggers create_check_in_messages callback" do
post :create, params
expect(response).to receive(:create_check_in_messages)
end
end
end
end
Here is my models/concerns/visit_message.rb callback file
module VisitMessage
extend ActiveSupport::Concern
included do
after_save :create_check_in_messages
end
def create_check_in_messages
. . .
end
end
Here is the fail message when I run 'rspec spec/controllers/guests_controller_spec.rb'
1) GuestsController#create when new guest is saved triggers create_check_in_messages callback
Failure/Error: post :create, params
ActionController::ParameterMissing:
param is missing or the value is empty: guest
# ./app/controllers/guests_controller.rb:63:in `guest_params'
# ./app/controllers/guests_controller.rb:10:in `create'
# ./spec/controllers/guests_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
I've been searching all over stackoverflow with no luck. I appreciate any help!
I am assuming that the guest_params method in the controller looks something like this:
def guest_params
params.require(:guest).permit(....)
end
If that is the case, you need to update the POST call in your test case thusly:
post :create, {guest: params}
On a side note, your controller is unnecessarily bloated. I would read up on working with associated models to streamline your code, specifically, using accepts_nested_attributes_for:
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I have a Rails 3 project in which I want to store the current company selected in a session variable.
I'm working with the staff controller spec and would like to stub out current_company for now as I'm isolating my spec example for the staff new controller action.
it "should call current_company" do
company = mock_model(Company, :id => "1")
controller.should_receive(:current_company).and_return(company)
get :new
end
Here is my new action for the staff controller
def new
#staff = Staff.new
#staff.company_id = current_company.id
end
I keep getting error
Failure/Error: get :new
NameError:
undefined local variable or method `current_company' for #<StaffsController:0x000000028d6ad8>
I've also tried just stubbing it out instead of using should_receive
controller.stub!(:current_company).and_return(company)
I get the same error.
Your code looks fine to me, it should work. There must be some other problem we are not seeing. I notice the controller name is "StaffsController" -- is that correct? Double-check the names of the controller and the corresponding spec -- they should be the same.
I think it was bombing out on the 'should be successful' example/test, so I've put my stubbing in a before block.
require 'spec_helper'
describe StaffsController do
describe "GET 'new'" do
let(:staff) { mock_model(Staff, :company_id= => nil)}
let(:company) { mock_model(Company, :id => 1)}
before do
Staff.stub!(:new).and_return(staff)
controller.stub!(:current_company).and_return(company)
end
it "should be successful" do
get :new
response.should be_success
end
it "should call current_company" do
controller.should_receive(:current_company).and_return(company)
get :new
end
end
end
This works for:
class StaffsController < ApplicationController
def new
#staff = Staff.new
current_company.id
end
end
I am trying to specify in my RSpec tests that my controller should use current_user.projects.find() instead of Project.find() I am using the Mocha mocking framework and was trying something like this:
controller.current_user.projects.expects(:find).returns(#project)
I have already mocked out controller.stubs(:current_user).returns(#profile)
This test passes with this even when I use the Project.find() implementation. How can I test that my controller is calling off of the correct object?
Edit (adding additional code):
I have Projects and Tasks, Project have many tasks. This is the show method for displaying a task in a project that is owned by current_user
Action in the controller:
def show
#project = current_user.projects.find_by_id(params[:cardset_id])
if #project.nil?
flash[:notice] = "That project doesn't exist. Try again."
redirect_to(projects_path)
else
#task = #project.tasks.find_by_id(params[:id])
end
end
This is the test that is not checking that the cardsets method was called off the current_user object.
Current Test:
context "with get to show" do
context "with valid project" do
before(:each) do
#project = Factory(:project)
#task = Factory(:task)
#profile = #project.profile
ApplicationController.stubs(:require_user).returns(true)
controller.stubs(:current_user).returns(#profile)
Project.stubs(:find_by_id).returns(#project)
#project.tasks.stubs(:find_by_id).returns(#task)
get :show, :project_id => #project.id, :id => #task.id
end
it "should assign task" do
assigns[:task].should_not be_nil
end
it "should assign project" do
assigns[:project].should_not be_nil
end
end
context "with invalid project" do
before(:each) do
Project.stubs(:find_by_id).returns(nil)
get :show, :project_id => #project.id, :id => #task.id
end
it "should set flash" do
flash[:notice].should match(/doesn't exist/i)
end
it "should redirect" do
response.should redirect_to(cardsets_url)
end
end
end
Based on the little you've told us, I think you need:
#profile.expects(:find).returns(#project)