How to trouble shoot test::unit errors? - ruby-on-rails

I have a test that creates the following error:
1) Failure:
test_should_get_create(ProductRequestsControllerTest) [/Users/noahc/Dropbox/mavens/test/functional/product_requests_controller_test.rb:37]:
"ProductRequest.count" didn't change by 1.
<2> expected but was
<1>.
How do I trouble shoot this? Specifically, how can I get a more specific detailed error?
Here is my test:
test "should get create" do
sign_in(FactoryGirl.create(:user))
assert_difference('ProductRequest.count') do
post :create, product_request: FactoryGirl.attributes_for(:product_request)
end
assert_response :success
end
and here is my controller:
def create
cart = current_cart
rows = CartRow.find_all_by_cart_id(cart.id)
rows.each do |row|
product_request = ProductRequest.new(params[:product_request])
product_request.user_id = current_user.id
product_request.product_id = row.product_id
product_request.quantity = row.quantity
product_request.save
end
redirect_to root_path
end
I believe the issue is that I don't have a cart defined. How do I create a cart that unit::test can see? I've tried using FactoryGirl to create a cart, but that didn't seem to work.
carts_factory.rb
FactoryGirl.define do
factory :cart do
end
end
Updated test:
test "should get create" do
sign_in(FactoryGirl.create(:user))
user = FactoryGirl.create(:user)
product = FactoryGirl.create(:product)
assert_difference('ProductRequest.count') do
post :create, product_request: FactoryGirl.attributes_for(:product_request, user: user.id, product: product.id)
end
assert_response :success
end
and current_cart
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end
Second Update
I've updated the factories as you've suggested.
Here is what my test now looks like:
test "should get create" do
user = FactoryGirl.create(:user)
cart = FactoryGirl.create(:cart_with_1_row)
product = FactoryGirl.create(:product)
sign_in(user)
product = FactoryGirl.create(:product)
assert_difference('ProductRequest.count') do
post :create, { product_request: FactoryGirl.attributes_for(:product_request, user_id: user.id, product_id: product.id, cart_id: cart.id) }
end
assert_response :success
end
Here's it in a test console:
irb(main):016:0> a = { product_request: FactoryGirl.attributes_for(:product_request, user_id: user.id, product_id: product.id, cart_id: cart.id) }
=> {:product_request=>{:quantity=>10, :street=>"123 street", :city=>"Some City", :state=>"Iowa", :zip=>"13829", :user_id=>1, :product_id=>2, :cart_id=>1}}

First of all, CartRow.find_all_by_cart_id(cart.id), this is not good design. Much better when you ask Cart model for its row, for example: rows = cart.rows
I think the issue is that you don't have rows inside you cart.
As I see you store cart id in session but when you call controller in test you does not provide session. You need create cart and cart's rows and when store cart_id in session before you call controller. And it is important to merge current session and session with cart_id. For example:
test "should get create" do
user = FactoryGirl.create(:user)
cart = FactoryGirl.create(:cart_with_1_row)
sign_in(user)
product = FactoryGirl.create(:product)
assert_difference('ProductRequest.count') do
post :create, { product_request: FactoryGirl.attributes_for(:product_request, user: user.id, product: product.id) }, { cart_id: cart.id }.merge(session)
end
assert_response :success
end
Also you need update your cart and cart's row factories:
FactoryGirl.define do
factory :cart do
factory :cart_with_1_row do
after(:create) do |cart|
FactoryGirl.create(:cart_row, cart: cart)
end
end
end
factory :cart_row do
cart
end
end
I think your CartRow model looks like:
class CartRow < ActiveRecord::Base
belongs_to :cart
end

The problem definitely seems to be coming from the cart.
If you don't want to deal with creating a cart in FactoryGirl (which I would recommend), you can just stub out the current_card in the test, and this would do the same thing.
However, mocking is much more complicated than creating the cart in FactoryGirl, and if you're planning on using this down the road, FactoryGirl is definitely the way to go.

Related

Failed test expectation

I have a carts controller in my app
class CartsController < ApplicationController
def show
#cart = Cart.find(session[:cart_id])
#products = #cart.products
end
end
and wrote test cartscontroller_spec.rb
RSpec.describe CartsController, type: :controller do
describe 'GET #show' do
let(:cart_full_of){ create(:cart_with_products, products_count: 3)}
before do
get :show
end
it { expect(response.status).to eq(200) }
it { expect(response.headers["Content-Type"]).to eql("text/html; charset=utf-8")}
it { is_expected.to render_template :show }
it 'should be products in current cart' do
expect(assigns(:products)).to eq(cart_full_of.products)
end
end
end
My factories.rb looks such:
factory(:cart) do |f|
f.factory(:cart_with_products) do
transient do
products_count 5
end
after(:create) do |cart, evaluator|
create_list(:product, evaluator.products_count, carts: [cart])
end
end
end
factory(:product) do |f|
f.name('__product__')
f.description('__well-description__')
f.price(100500)
end
but I have got an error:
FCartsController GET #show should be products in current cart
Failure/Error: expect(assigns(:products)).to eq(cart_full_of.products)
expected: #<ActiveRecord::Associations::CollectionProxy [#<Product id: 41, name: "MyProduct", description: "Pro...dDescription", price: 111.0, created_at: "2016-11-24 11:18:43", updated_at: "2016-11-24 11:18:43">]>
got: #<ActiveRecord::Associations::CollectionProxy []>
Looks like I have no created products at all because of empty product model array ActiveRecord::Associations::CollectionProxy [], simultaneously, I investigate product`s id is increasing with every test attempt.At the moment I have no solid ideas that is wrong
The id of the created cart is not assigned to the session of your get :show.
before do
session[:cart_id] = cart_full_of.id
get :show
end
# or
before do
get :show, session: { cart_id: cart_full_of.id }
end
UPDATE:
Your find in the controller need the session[:cart_id] value, but your test didn't provide this data to the controller request. If you use one of the codes above the test request provides the session to the controller.

Rspec method is being called 2X, but can't find second time

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...

Object.save failed in spec data validation

Here is the failed spec code for create in customer controller:
describe CustomersController do
before(:each) do
#the following recognizes that there is a before filter without execution of it.
controller.should_receive(:require_signin)
controller.should_receive(:require_employee)
end
render_views
describe "'create' successful" do
before(:each) do
category = Factory(:category)
sales = Factory(:user)
#customer = Factory.attributes_for(:customer, :category1_id => category.id, :sales_id => sales.id)
session[:sales] = true
session[:user_id] = sales.id
session[:user_name] = sales.name
session[:page_step] = 1
session['page1'] = customers_path
end
it "should create one customer record" do
lambda do
post 'create', #customer
end.should change(Customer, :count).by(1)
end
it "should redirect to customers path" do
put 'create', #customer
flash[:notice].should_not be_nil
response.should redirect_to(customers_path)
end
end
end
The customer has both sales id and category id which belong to user and category table respectively.
Here is the spec failure error:
1) CustomersController GET customer page 'create' successful should create one customer record
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/customers_controller_spec.rb:37:in `block (4 levels) in <top (required)>'
2) CustomersController GET customer page 'create' successful should redirect to customers path
Failure/Error: flash[:notice].should_not be_nil
expected: not nil
got: nil
# ./spec/controllers/customers_controller_spec.rb:44:in `block (4 levels) in <top (required)>'
Here is the app code for create in customer controller:
def create
if session[:sales]
#customer = Customer.new(params[:customer], :as => :roles_new_update)
#customer.sales_id = session[:user_id]
if #customer.save
#message = "New customer #{params[:name]} was created. Please check it out"
#subject = "New customer #{params[:name]} was created BY {#session[:user_name]}"
UserMailer.notify_tl_dh_ch_ceo(#message, #subject, session[:user_id])
redirect_to session[('page' + session[:page_step].to_s).to_sym], :notice => 'Customer was created successfaully!'
else
render 'new', :notice => 'Customer was not saved!'
end
end
end
Here is the code in factories.rb:
Factory.define :customer do |c|
c.name "test customer"
c.short_name "test"
c.email "t#acom.com"
c.phone "12345678"
c.cell "1234567890"
c.active 1
c.category1_id 2
c.sales_id 1
c.address "1276 S. Highland Ave, Lombard, IL 67034"
c.contact "Jun C"
end
Factory.define :category do |c|
c.name "category name"
c.description "test category"
c.active true
end
Factory.define :user do |user|
user.name "Test User"
user.email "test#test.com"
user.password "password1"
user.password_confirmation "password1"
user.status "active"
user.user_type "employee"
end
It seems that the error was caused by #customer.save returning false and the code for "if #customer.save" was not executed. So the problem may be with the #customer generated by Factory which seems good to me. The code is executed without any problem when saving a customer.
Any suggestions? Thanks.
I would break this up into two specific tests. Right now you're unsure of two things:
is the customer is being told to save itself?
Is there a validation that is preventing customer from being saved?
The quickest path is to change #customer.save to #customer.save! and see if there are any exceptions raised (it will do so if a validation failed).
I recommend you split this up though. To test #1, in the controller spec:
it "should tell the customer to save itself when there is a session[:sales]" do
session[:sales] = true
customer_mock = double(:customer)
customer_mock.should_receive(:sales_id=)
customer_mock.should_receive(:save).and_return(:true)
Customer.stub(:new => cutomer_mock)
post 'create'
end
Then in your customer_spec, test out:
it "should be valid with factory specs" do
customer = Customer.new(Factory.attributes_for(:customer))
customer.should be_valid
end
post :create, :customer => #customer
solves the problem with above.

Can I have some feedback with rspec when writing controllers specs?

I was wondering if i could have some feedbacks with the controller spec bellow. In fact i'm new when writing specs and controller's spec are way different from model's spec ! So i'm wondering if i may not go in the wrong direction...
subjects_controller.rb
def show
#subject = Subject.find(params[:id])
if #subject.trusted?(current_user)
#messages = #subject.messages
else
#messages = #subject.messages.public
#messages = #messages + #subject.messages.where(:user_ids => current_user.id)
#messages.uniq!
end
# sort the list
#messages = #messages.sort_by(&:created_at).reverse
if !#subject.company.id == current_user.company.id
redirect_to(subjects_path, :notice => "Invalid subject")
end
end
subjects_controller_spec.rb
require 'spec_helper'
describe SubjectsController do
before(:each) do
#subject = mock_model(Subject)
end
context "for signed users" do
before(:each) do
#current_user = sign_in Factory(:user)
end
context "GET #show" do
before(:each) do
Subject.stub!(:find, #subject).and_return(#subject)
end
context "when current_user is trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(true)
#subject.should_receive(:messages).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when current_user is not trusted" do
before(:each) do
messages = []
company = mock_model(Company)
#subject.should_receive(:trusted?).and_return(false)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:public).and_return(messages)
#subject.should_receive(:messages).and_return(messages)
messages.should_receive(:where).and_return(messages)
#subject.should_receive(:company).and_return(company)
end
it "should render success" do
get :show, :id => #subject
response.should be_success
end
end
context "when subject's company is not equal to current_user's company" do
# I have no idea of how to implement ==
end
end
end
end
Factories.rb
Factory.define :user do |u|
u.first_name 'Test User' #
u.username 'Test User' #
u.surname 'TheTest' #
u.email 'foo#foobar.com' #
u.password 'please' #
u.confirmed_at Time.now #
end
As far as I can tell you're on the right path. The basic idea is to completely isolate your controller code from model and view in these tests. You appear to be doing that--stubbing and mocking model interaction.
Don't write RSpec controller specs at all. Use Cucumber stories instead. Much easier, and you get better coverage.

rspec problem with mocking some objects

I'm seeing some strange behavior when I'm trying to stub out some methods. I'm using rails 3.0.3 and rspec 2.3.0
Here is the relevant section of the spec file
require 'spec_helper'
include Authlogic::TestCase
describe PrizesController do
before do
activate_authlogic
#manager = Factory.create(:valid_manager, :name => "Test Manager ")
UserSession.create #manager
end
def mock_prize(stubs={})
(#mock_prize ||= mock_model(Prize, :point_cost => 100).as_null_object).tap do |prize|
prize.stub(stubs) unless stubs.empty?
end
end
def mock_consumable(stubs={})
(#mock_consumable ||= mock_model(Consumable).as_null_object).tap do |consumable|
consumable.stub(stubs) unless stubs.empty?
end
end
describe "GET buy_this" do
it "assigns the requested prize as #prize and requested consumable as #consumable if the player has enough points" do
Prize.stub(:find).with("37") { mock_prize }
#manager.should_receive(:available_points).and_return(1000)
get :buy_this, :id => "37", :user_id => #manager.id
assigns(:prize).point_cost.should eq(100)
assigns(:prize).should be(mock_prize)
assigns(:consumable).should_not be_nil
end
it "assigns the requested prize as #prize and no consumable as #consumable if the player does not have enough points" do
Prize.stub(:find).with("37") { mock_prize }
#manager.should_receive(:available_points).and_return(10)
get :buy_this, :id => "37", :user_id => #manager.id
assigns(:prize).point_cost.should eq(100)
assigns(:prize).should be(mock_prize)
assigns(:consumable).should be_nil
end
end
And the controller method:
def buy_this
#prize = Prize.find(params[:id])
user = User.find(params[:user_id]) if params[:user_id]
user ||= current_user
flash[:notice] = ("Attempting to redeem points for a prize")
if user.available_points > #prize.point_cost
#consumable = user.consumables.create(:kind => #prize.consumable_kind, :description => #prize.consumable_description, :redemption_message => #prize.consumable_redemption_message)
point_record = #consumable.create_point_record(:redeemed_points => #prize.point_cost)
point_record.user = user
point_record.save
flash[:success] = "You successfully redeemed #{#prize.point_cost} points for #{#prize.name}"
else
flash[:error] = "Sorry, you don't seem to have enough points to buy this"
end
redirect_to prizes_path
end
The tests fail and this is the output...
1) PrizesController GET buy_this assigns the requested prize as #prize and requested consumable as #consumable if the player has enough points
Failure/Error: assigns(:consumable).should_not be_nil
expected not nil, got nil
# ./spec/controllers/prizes_controller_spec.rb:39
2) PrizesController GET buy_this assigns the requested prize as #prize and no consumable as #consumable if the player does not have enough points
Failure/Error: #manager.should_receive(:available_points).and_return(10)
(#<User:0x10706b000>).available_points(any args)
expected: 1 time
received: 0 times
# ./spec/controllers/prizes_controller_spec.rb:44
Any ideas about this? I'm totally stumped why the two tests calling the same method with the same parameters would fail in different ways (not to mention, I don't understand why they are failing at all...).

Resources