I am writing a spec test in rails using rspec. One of the models I am testing has an attribute of sequence on it, and it is conflicting with rspec (from what I have found), but I have not been able to find a work around.
Here is my code
lessons.rb (factory)
FactoryGirl.define do
factory :lesson do
version_id 1
add_attribute :sequence, 1 # this is the field
name "Lesson Name"
description "An Example Lesson Description"
group_id 1
content "here is some example context"
passing_quiz_difficulty 2
survey_id 1
status 1
staff_content "example staff content"
lesson_type_id 1
end
end
method I'm testing
def second_response?(lesson)
self.lessons.where('sequence < ?', lesson.sequence).first
end
controller
def show
...
#part_two = true if #reveal_step.second_response?(#lesson)
...
end
rspec stack trace
Failure/Error: expect(#reveal_step.second_response?(lesson_1.id)).to eql true
NoMethodError: undefined method `sequence' for 1:Fixnum
This method works fine in the application, but I need to write a test for it, and I cannot find a work around with the sequence attribute, and I cannot leave it out because this whole method and flow of this part of the app is dependent on the sequence attr. Does anyone know of a workaround to use? or another way I could approach this? Any help would be appreciated.
Your expectation is passing a number, lesson_1.id, into a method that expects a lesson. That ID does not have a sequence method, while lessons do.
Related
I have an RSpec test that uses factory_bot to create instances. The test passes except when the first method is used in a view.
This is the code being tested:
def order_confirm_email(id, items, order, address, coupon)
#user = User.find(id)
#items = items
#order = order
#address = address
if coupon == nil
#coupon = ''
else
#coupon = coupon.discount
end
mail(to: #user.email, subject: 'Order completed')
end
This is the test:
it 'sends an email upon checkout process completion' do
user = create(:user)
items = create(:base_item)
order = create(:base_item)
address = create(:address)
coupon = create(:coupon)
expect(orderConfirmationMail.subject).to eq('Order completed')
end
So far so good. But when one of the views attempts to access the first instance, as below:
<h1>Order id - <%= #order.first.id %></h1>
Then I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon checkout process completion
Failure/Error: <!-- <h1>Order id - <%= #order.first.id %></h1> -->
ActionView::Template::Error:
undefined method `first' for 3:Fixnum
According to my understanding, the instances should be persisting since I use create instead of build. But apparently that is not happening. Changing the view is not an option except as a last resort. How do I resolve this?
UPDATE 1:
This is the order_items.rb Factory file:
FactoryBot.define do
factory :base_item, class: OrderItem do
item_name_en "Sample Item"
item_link "http://www.foo.com"
qty 5
available_qty 100
item_price 10
seller_name "foo"
status "foo"
foo_item_id "123456"
foo_id "654321"
end
factory :order_item1, parent: :base_item do
foo_item_id "123456"
foo_seller_id "654321"
association :order
end
factory :order_item2, parent: :base_item do
foo_item_id "123457"
foo_seller_id "654321"
end
factory :order_item3, parent: :base_item do
foo_item_id "123458"
foo_seller_id "654322"
end
end
UPDATE 2:
I am encountering a similar issue when writing another test. I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon abandoned cart
Failure/Error: <% #items.each do |item| %>
ActionView::Template::Error:
undefined method `each' for 1:Fixnum
This seems to indicate that it is not an issue with the first method per se, but rather with the interpolated Ruby in the views throwing an error when I run RSpec. This generalizes the problem and hopefully makes it more easily solvable.
that is not an issue, as earlier in the spec I include this line that I know is working correctly because I use the same format in another test successfully
let(:orderConfirmationMail) { UserMailer.order_confirm_email(1,2,3,4,nil) }
There it is. If you included this line from the start, your question would've been answered in two seconds.
Considering the signature of order_confirm_email
def order_confirm_email(id, items, order, address, coupon)
Why do you think order is set to 3? Because you pass it this way!
Mystery solved.
sorry, I don't have any idea. I would give a look to your OrderItem model, also I don't get why you do #order.first because that is just 1 object. Could make sense to me #orders.first but not #order.first. When things don't make sense in rails it is hard to build applications. also order and item should be two different models, we use order_items for building joins between two different models
An order can have many or just one item, you decide the relationship.
with rspec you can debug and also you can test your factories in the rails c test environment. There you can check what is the result of create(:base_item) or of FactoryGirl.create(:base_item)
I believe that should be an object, so I don't understand why it is saying undefined method 'first' for 3:Fixnum
Maybe the you factory is not what we expect
but you can understand this by debugging and testing in the console your factory
I have an rspec that looks like:
it "should test nil" do
u = Upload.new
u.save!
u.my_method!
end
The method looks like:
class Upload < ActiveRecord::Base
def my_method!
puts "Made it to my method :)"
not_defined_var
puts "But didn't get this far :("
end
end
Clearly, not_defined_var should give a "NameError: undefined local variable or method".
My output prints
Made it to my method :)
.
Finished in 1.54 seconds
1 example, 0 failures
I feel like I'm missing something essential. Got any ideas?
You are not calling method expect so Rspec is just passing. To compare if anything goes as expected you must call it. To test code you must compare the response with what you expect.
Example:
RSpec.describe Account do
it "has a balance of zero when first created" do
expect(Account.new.balance).to eq(Money.new(0))
end
end
https://relishapp.com/rspec/rspec-expectations/v/3-3/docs
Here is my Lesson model:
before_create :set_sequence
def set_sequence
maxseq = Lesson.where(:course_id => self.course_id).maximum("sequence")
if (maxseq.nil?)
maxseq = 0
end
self.sequence = maxseq + 1
end
when I run rspec the following test fails:
it "validate sequence is setup" do
lesson = Lesson.create(:title => "Testing", :description => "Testing", :course_id => 1)
lesson.sequence.should_not eql nil
end
However when T test this through rails console the Lesson object is created successfully and with the correct sequence. Any ideas why?
lesson.sequence.should_not be_nil is the correct way to test for nil, as far as I know. Have you tried that?
Any validations you've got on Lesson could be silently aborting the create before your callback gets called. Change it to create! in the spec to check it.
FactoryGirl first initializes object with no parameters, and then assigns parameters one by one. The callback in your model probably would not work in this case. So you can try to change FactoryGirl's behavior by adding
initialize_with { new(attributes) }
to the Lesson's factory. I'm not sure it will help though. Testing callback behavior in Rails is tricky.
I'm writing an app with Rails 3. In my functional test, test/functional/cust_infos_controller_test.rb, I have these:
require 'test_helper'
class CustInfosControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "should get cust" do
get :customerinfo
assert_response :success
assert_not_nil assigns("cust_infos")
end
end
My controller is fairly straightforward as it just finds all the customer's info:
class CustInfosController < ApplicationController
def customerinfo
#cust_info = CustInfo.find(:all, :order=>"cust_id")
#cust_info.each do |ci|
if ci.upload_freq == 'W'
ci.upload_freq = 'Weekly'
elsif ci.upload_freq == 'M'
ci.upload_freq = 'Monthly'
end
end
end
end
When I ran it with:
$ ruby -Itest test/functional/cust_infos_controller_test.rb
I got the following error:
Loaded suite test/functional/cust_infos_controller_test
Started
E
Finished in 0.025258 seconds.
1) Error:
test_should_get_cust(CustInfosControllerTest):
ActiveRecord::StatementInvalid: PGError: ERROR: numeric field overflow
DETAIL: A field with precision 4, scale 0 must round to an absolute value less than 10^4.
: INSERT INTO "cust_infos" ("cust_id") VALUES (298486374)
1 tests, 0 assertions, 0 failures, 1 errors
In my cust_infos table, I have cust_id as integer. But I don't know why when I ran controller test to just get some record, active record will execute insert statement. How do I fix this?
UPDATE: I commented out all the lines in customerinfo method, and run the same test. Got the exact same result. So I'm guessing it's not my methods behavior, but rather Rails? Any hint?
I see what you are trying to do. What you need to do is create a helper for your view.
application_helper.rb
def display_upload_freq(s)
case s
when 'W'
'Weekly'
when 'M'
'Monthly'
end
end
cust_info_controller.rb
class CustInfosController < ApplicationController
def customerinfo
#cust_info = CustInfo.find(:all, :order=>"cust_id")
end
end
Then in your view when your are iterating through #cust_info, for upload_freq, use dislay_upload_freq(ci.upload_freq). Assuming you have #cust_info.each do |ci|. Then you won't be confusing the db with saving anything.
Are you using factories or fixtures to create your test data? If not, how are you creating it? My guess is that the insert is happening when your test data is being set up, not because of anything happening in your controller. The second part of that guess is borne out by the fact that commenting out all code in your controller isn't getting rid of the error.
I know this post is quite old, but I will post my answer in case someone is brought here by search engines.
I had the exact same problem, the issue originates from the fixture file(s) in test/fixtures/{name_of_your_model}.yml. Rails adds some initial values in this file, in my case it looked like this:
one: {}
# column: value
#
two: {}
# column: value
and when you want to run the test, it will try to create the test database using this fixtures. And that's where the issue occurs. Trying to create an empty record while you did not allow a null ID in your table.
Deleting these fixtures, or filling them with appropriate values should solve the problem.
I have a test more or less like this:
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
# ...
I've purposely added a
raise "blah"
somewhere down the road and I get this error:
RuntimeError: blah
test/unit/form_definition_test.rb:79:in `__bind_1290079321_362430'
when I should be getting something along:
/Users/pupeno/projectx/db/seed/sheet_definitions.rb:17:in `sheet_definition': blah (RuntimeError)
from /Users/pupeno/projectx/db/seed/form_definitions.rb:4:in `form_definition'
from /Users/pupeno/projectx/test/unit/form_definition_test.rb:79
Any ideas what is sanitizing/destroying my backtraces? My suspicious is shoulda because the when the exception happens inside a setup or should is whet it happens.
This is a Rails 3 project, in case that's important.
That is because the shoulda method #context is generating code for you. for each #should block it generates a completely separate test for you so e.g.
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
end
should "verify some condition" do
assert something
end
should "verify some other condition" do
assert something_else
end
end
end
Then #should will generate two completely independent tests (for the two invocations of #should), one that executes
#definition = SeedData.form_definition
assert something
and another one that executes
#definition = SeedData.form_definition
assert something_else
It is worth noting that it does not generate one single test executing all three steps in a sequence.
These generated blocks of codes have method names like _bind_ something and the generated test have name that is a concatenation of all names of the contexts traversed to the should block plus the string provided by the should block (prefixed with "should "). There is another example in the documentation for shoulda-context.
I think this will give you the backtrace that you want. I haven't tested it, but it should work:
def exclude_backtrace_from_location(location)
begin
yeild
rescue => e
puts "Error of type #{e.class} with message: #{e.to_s}.\nBacktrace:"
back=e.backtrace
back.delete_if {|b| b~=/\A#{location}.+/}
puts back
end
end
exclude_backrace_from_location("test/unit") do
#some shoulda code that raises...
end
Have you checked config/initializers/backtrace_silencers.rb? That is the entry point to customize that behavior. With Rails.backtrace_cleaner.remove_silencers! you can cleanup the silencers stack.
More informations about ActiveSupport::BacktraceCleaner can be found here.