Rails Factory Girl RSpec Testing Has-Many Relationship of Nested Resources - ruby-on-rails

Note: A business has many catalogs and has products, and a catalog has many products. The associations are properly defined and they are working in the application front end. But I can't make this test pass. I am using friendly_id so you will see me using #model.slug on some of the find methods
I am trying this test out:
describe "GET 'show'" do
before do
#business = FactoryGirl.create(:business)
#catalog = FactoryGirl.create(:catalog, :business=>#business)
#product1 = FactoryGirl.create(:product, :business=>#business, :catalog=>#catalog)
#product2 = FactoryGirl.create(:product, :business=>#business, :catalog=>#catalog)
end
def do_show
get :show, :business_id=>#business.slug, :id=>#catalog.slug
end
it "should show products" do
#catalog.should_receive(:products).and_return([#product1, #product2])
do_show
end
end
with this factory (note that a business and catalog factory is define somewhere else, and they are associations):
FactoryGirl.define do
sequence :name do |n|
"product#{n}"
end
sequence :description do |n|
"This is description #{n}"
end
factory :product do
name
description
business
catalog
end
end
with this show action:
def show
#business = Business.find(params[:business_id])
#catalog = #business.catalogs.find(params[:id])
#products = #catalog.products.all
respond_with(#business, #catalog)
end
but I am getting this error:
CatalogsController GET 'show' should show products
Failure/Error: #catalog.should_receive(:products).and_return([#product1, #product2])
(#<Catalog:0x000001016185d0>).products(any args)
expected: 1 time
received: 0 times
# ./spec/controllers/catalogs_controller_spec.rb:36:in `block (3 levels) in <top (required)>'
furthermore, this code block will also indicate that Business model has not received the find method:
Business.should_receive(:find).with(#business.slug).and_return(#business)

The problem here is that the #catalog instance variable you set up in the spec is not the same as the #catalog instance variable in the controller.
#catalog in the spec will never receive any messages sent to #catalog in the controller.
What you need to do instead is to change this in your spec:
#catalog.should_receive(:products).and_return([#product1, #product2])
to
Catalog.any_instance.should_receive(:products).and_return([#product1, #product2])
Check out the RSpec documentation on any_instance.should_receive here: https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/message-expectations/expect-a-message-on-any-instance-of-a-class

Related

testing nested attributes in rails

before I ask the question I want to give a little background on the models. I have a user_conversation model(through table) which accepts attributes from conversations and messages models. The create action and before action are given below.
before_action :logged_in_user
before_action :validate_conversation, only: :create
def create
redirect_to home_path unless current_user
#conversation = UserConversation.new conversation_params
#conversation.user = current_user
#conversation.conversation.messages.first.user = current_user
#conversation.save!
activate_unread
redirect_to user_conversation_path(current_user,#conversation)
end
Private
def validate_conversation
#user = User.find params[:user_id]
if params[:user_conversation][:conversation_attributes]["subject"].blank?
redirect_to new_user_conversation_path(#user)
flash[:danger] = "Subject cannot be blank"
else params[:user_conversation][:conversation_attributes][:messages_attributes]["0"]["body"].blank?
redirect_to new_user_conversation_path(#user)
flash[:danger] = "Message cannot be blank"
end
end
def conversation_params
params.require(:user_conversation).permit(:recipient_id, conversation_attributes: [:subject, messages_attributes: [:body]])
end
I was trying to write an integration tests for the post request of user_conversation. The test is given below.
require 'test_helper'
class ConversationCreateTest < ActionDispatch::IntegrationTest
def setup
#user = users(:user_a)
#conversation = conversations(:convo_one)
end
test "invalid creation of a user conversation no subject" do
log_in_as(#user)
get new_user_conversation_path(#user)
post user_conversations_path(#user), user_conversation: {:recipient_id => #user.id, :conversation_attributes => {:subject => "this is a subject",
:message_attributes => {"0" => {:body => "sending a message"}}}}
end
I get the following error message when I run the command.
1) Error:
ConversationCreateTest#test_invalid_creation_of_a_user_conversation_no_subject:
NoMethodError: undefined method `[]' for nil:NilClass
app/controllers/user_conversations_controller.rb:63:in `validate_conversation'
test/integration/conversation_create_test.rb:13:in `block in <class:ConversationCreateTest>'
191 runs, 398 assertions, 0 failures, 1 errors, 0 skips
I have been trying to debug the problem for about 2 hours. I have checked the test log files and it says internal server error 500. I have tried commenting certain lines of codes to narrow down the problem but not really sure what the problem is. Any help would be appreciated.
In rails, validations are made with the ActiveModel::Validators.
So you can simply validate your model like this:
User:
class User
has_many :conversations, through: "user_conversations"
end
Conversation:
class Conversation
has_many :users, through: "user_conversations"
validates_presence_of :subject, :messages
end
See more here about validations
So if you then need to validate your model you can call:
conversation = Conversation.create(subject: nil)
conversation.errors.full_messages # => ["Subject can't be blank"]
I think you'll need to rewrite a bunch of things in your app, and if you took the code above you can simply test this thing within a model (unit) test.
Which, by the way, is no longer needed because you don't want to test the rails provided validators. You probably just want to test your own validators.

MiniTest parent and child from factory in test

I'm getting the following error when running my test.
NoMethodError: undefined method 'departure_date' for nil:NilClass
Here is the test.
test "successful disbursement should respond with success" do
post :disbursement, {id: #invoice.invoice_id, trip: attributes_for(:trip)}
assert_response :success
end
In the setup portion of the test I'm creating the following.
setup do
#controller = Api::V1::InvoicesController.new
#invoice = create(:invoice)
#trip = create(:trip)
end
The trip factory looks like this.
FactoryGirl.define do
factory :trip do
depart_airport "MCI"
arrive_airport "ORD"
passenger_first_name "Joe"
passenger_last_name "Business"
passenger_count 1
departure_date {10.days.from_now}
invoice
end
end
The invoice factory looks like this.
FactoryGirl.define do
factory :invoice do
sequence(:invoice_id) { |n| "INV#{n}"}
merchant
amount 500.00
item_count 5
paid false
currency "GBP"
invoice_type "pre-flight"
end
end
Can't quite figure out how to make sure the invoice has a trip. I'm guessing this is why the test can't find the departure_date it should.
From what I understand, you are trying to associate the trip and invoice. If my understanding is correct, try this.
setup do
#controller = Api::V1::InvoicesController.new
#invoice = create(:invoice)
#trip = create(:trip, invoice: #invoice)
end

FactoryGirl has many trait creation not working

I'm trying to get FactoryGirl to create quiz_question. But it's only creating the belongs to factory(quiz_campaign) in the test DB environment, not the has many factory(quiz_question).
quiz_campaign.rb
FactoryGirl.define do
trait :with_quiz_questions do
after_create do |campaign|
FactoryGirl.create(:quiz_question, quiz_campaign: campaign)
end
end
end
factory :quiz_campaign do
subdomain "macy"
end
quiz_question.rb
FactoryGirl.define do
factory :quiz_question do
message "What's your question"
end
end
quiz_home_page.feature
Given(/^there's a subdomain for a quiz campaign$/) do
#quiz_campaign = create(:quiz_campaign)
end
When(/^I visit the quiz campaign microsite subdomain url$/) do
visit_with_subdomain microsite_home_path, subdomain: #quiz_campaign.subdomain
end
Resolved it by not using traits. It seems like using traits causes more complex solution than needed

undefined method `stringify_keys' while using Factory Girl

I have the following block of code in my User_spec.rb:
#user = { username:'newuser',
email:'new#user.com',
fname:'new',
lname:'user',
password:'userpw',
password_confirmation:'userpw'}
for creating a using using these attributes. However while I moved all these attributes to Factories.rb:
require 'factory_girl'
Factory.define :user do |u|
u.username 'newuser'
u.email 'new#user.com'
u.fname 'new'
u.lname 'user'
u.password 'newuserpw'
u.password_confirmation 'newuserpw'
end
and replace the line in user_spec.rb with:
#user = Factory(:user)
all my tests that related to the User model failed(such as tests for email, password, username etc), all were giving me
"undefined method `stringify_keys' for…"
the new user object
I had a similar problem, and it was because I was passing a FactoryGirl object to the ActiveRecord create/new method (whoops!). It looks like you are doing the same thing here.
The first/top #user you have listed is a hash of values, but the second/bottom #user is an instance of your User ojbect (built by FactoryGirl on the fly).
If you are calling something like this in your specs:
user = User.new(#user)
The first (hashed) version of #user will work, but the second (objectified) version will not work (and throw you the 'stringify_keys' error). To use the second version of #user properly, you should have this in your specs:
user = Factory(:user)
Hope that helps.
We need to see an example of a failing test to diagnose, but here is one thing that can cause it – sending an object when attributes are required. I once fixed one of my failing tests by changing:
post :create, organization: #organization
to
post :create, organization: #organization.attributes
#rowanu Answered your question, but let me layout my example too for future reference:
What was failing in my case was:
#user = User.new user_attr
#user.bookings_build(Booking.new booking_attr)
Note that I am trying to build with a booking instance and not hash of attributes
The working example:
user_attr_hash = FactoryGirl.attributes_for(:user)
booking_attr_hash = FactoryGirl.attributes_for(:booking)
#user = User.new user_attr_hash
#user.bookings.build(booking_attr_hash)
And in spec/factories/domain_factory.rb I have
FactoryGirl.define do
factory :user do
# DEFAULT USER...
password "123123123"
email "factory_girl#aaa.aaa"
# there rest of attributes set...
end
factory :booking do
start_date Date.today
end_date Date.today+3
# the rest of attributes
end
end

Mocking a non database backed model in Rails with Rspec

I am trying to create a non ActiveRecord model in my Ruby on Rails application according to http://railscasts.com/episodes/121-non-active-record-model. I am facing hard time testing it though.
I have following class in my app/models/sms.rb file.
class Sms
def initialize
# Do stuff here
end
def deliver
# Do some stuff here
end
end
I am unable to mock deliver method on my Sms class.
it 'should do someting' do
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
post :create, :user_id => #user.id
flash[:notice].should == "SMS sent successfully."
response.should redirect_to(some_url)
end
In my controller code I do have a line that says #sms.deliver. Yet above gives following error:
Failure/Error: #sms.should_receive(:deliver).and_return(true)
(Mock Sms).deliver(any args)
expected: 1 time
received: 0 times
Any pointers?
Variables beginning with # are instance variables. The #sms your controller refers to is not the same #sms as your spec has defined.
Try changing
#sms = mock(Sms)
#sms.should_receive(:deliver).and_return(true)
to
Sms.any_instance.should_receive(:deliver).and_return(true)
If your version of RSpec doesn't have the any_instance method, you'll need to stub :new on the class:
#sms = mock(Sms)
Sms.stub(:new).and_return(#sms)
#sms.should_receive(:deliver).and_return(true)

Resources