Rails nested models failing validation - ruby-on-rails

I'm writing model spec tests using rspec for nested models 3 levels deep. Each -> represents a has_many relationship.
Users->Goals->Milestones
Right now, my spec/models/milestone_spec.rb test is failing a be_valid check, and I'm a little stumped as to why this is happening.
Failures:
1) Milestone
Failure/Error: it { should be_valid }
expected valid? to return true, got false
# ./spec/models/milestone_spec.rb:17:in `block (2 levels) in <top (required)>'
The spec itself:
describe Milestone do
let(:user) { FactoryGirl.create(:user) }
let(:goal) { user.goals.build(content: "Loreum Ipsum", amount: "30", interval: 2) }
before do
#milestone = goal.milestones.build(amount: "20")
end
subject { #milestone }
it { should respond_to(:goal_id) }
it { should respond_to(:amount) }
# not sure why this isn't working
it { should be_valid }
Could it be with how I'm creating the #milestone? I've tried goal.milestones.new, and that doesn't seem to make a difference. Below in the spec I have some tests for fields properly validating, and those run fine.
Any ideas?

Try this:
it "should be valid" do
#milestone.valid?
puts #milestone.errors.full_messages
end
That will run the validations and print out the the validation errors for you so you can see what's going on.

Related

Ruby on Rails - Testing Mailer as a callback in Model

I have this logic where I send a welcome email to the user when it is created.
User.rb
class User < ApplicationRecord
# Callbacks
after_create :send_registration_email
private
def send_registration_email
UserConfirmationMailer.with(user: self).user_registration_email.deliver_later
end
end
I tried testing it in user_spec.rb:
describe "#save" do
subject { create :valid_user }
context "when user is created" do
it "receives welcome email" do
mail = double('Mail')
expect(UserConfirmationMailer).to receive(:user_registration_email).with(user: subject).and_return(mail)
expect(mail).to receive(:deliver_later)
end
end
end
But it is not working. I get this error:
Failure/Error: expect(UserConfirmationMailer).to receive(:user_registration_email).with(user: subject).and_return(mail)
(UserConfirmationMailer (class)).user_registration_email({:user=>#<User id: 5, email: "factory10#test.io", created_at: "2023-02-18 01:09:34.878424000 +0000", ...878424000 +0000", jti: "2163d284-1349-4e48-8a2a-1b52b578921c", username: "jose_test10", icon_id: 5>})
expected: 1 time with arguments: ({:user=>#<User id: 5, email: "factory10#test.io", created_at: "2023-02-18 01:09:34.878424000 +0000", ...878424000 +0000", jti: "2163d284-1349-4e48-8a2a-1b52b578921c", username: "jose_test10", icon_id: 5>})
received: 0 times
Am I doing something wrong when testing the action of sending the email in a callback?
PD.
My environment/test.rb config is as follows:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
config.action_mailer.default_url_options = { :host => "http://localhost:3000" }
Even if I change config.active_job.queue_adapter to :test following this, I get the same error.
Another way I am trying to do it is like this:
expect { FactoryBot.create(:valid_user) }.to have_enqueued_job(ActionMailer::MailDeliveryJob).with('UserConfirmationMailer', 'user_registration_email', 'deliver_now', subject)
But then subject and the user created in FactoryBot.create(:valid_user) is different..
Any ideas are welcome. Thank you!
The stubbing in the question doesn't work because it doesn't stub the chain of methods in the same order then they are called in the implementation.
When you want to stub this method chain
UserConfirmationMailer.with(user: self).user_registration_email.deliver_later
then you have to first stub the with call, then the user_registration_email and last the deliver_later.
describe '#save' do
subject(:user) { build(:valid_user) }
let(:parameterized_mailer) { instance_double('ActionMailer::Parameterized::Mailer') }
let(:parameterized_message) { instance_double('ActionMailer::Parameterized::MessageDelivery') }
before do
allow(UserConfirmationMailer).to receive(:with).and_return(parameterized_mailer)
allow(parameterized_mailer).to receive(:user_registration_email).and_return(parameterized_message)
allow(parameterized_message).to receive(:deliver_later)
end
context 'when user is created' do
it 'sends a welcome email' do
user.save!
expect(UserConfirmationMailer).to have_received(:with).with(user: user)
expect(parameterized_mailer).to have_received(:user_registration_email)
expect(parameterized_message).to have_received(:deliver_later)
end
end
end
Note: I am not sure if using instance_double will work in this case, because the Parameterized uses method_missing internally. Although instance_double is usually preferred, you might need to use double instead.

RSpec API controllers testing

At first, sorry for my English :)
I need to realize API controller's tests in Ruby on Rails application (v 4.2.0).
When I do request to GET Advertising Sources I have a json response like this:
{"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]}
JSON response template was defined by front-end developer.
Now I trying to create tests for:
1. JSON size (2 advert sources)
2. included attributes (id, title)
My tests:
it 'returns list of advertising sources' do
expect(response.body).to have_json_size(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
hash_body = JSON.parse(response.body)
expect(hash_body).to include(attr)
end
end
Failures:
1. Failure/Error: expect(response.body).to have_json_size(2)
expected {"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]} to respond to `has_json_size?`
2. Failure/Error: expect(hash_body).to include(attr)
expected {"advertising_sources" => [{"id" => 71, "title" => "necessitatibus"}, {"id" => 72, "title" => "impedit"}]} to include "id"
Diff:
## -1,2 +1,2 ##
-["id"]
+"advertising_sources" => [{"id"=>71, "title"=>"necessitatibus"}, {"id"=>72, "title"=>"impedit"}],
Can anyone help me to correctify my tests code?
Thanks!
Given the shape of your response and the characteristics you are interested in testing, you can write your tests as follows:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
advertising_sources.each { |source| expect(source.keys).to include(attr) }
end
end
end
I would personally simplify this even further to:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
it 'includes an id and title for each source' do
advertising_sources.each { |source| expect(source.keys).to match_array(%w(id title)) }
end
end

Rspec testing validation

I am new to rails and I'm trying to understand testing using Rspec. I created a simple model (just for testing purposes) named Contact that contains firstname:string, lastname:string and email:string. I am just trying to set a test for firstname to fail if is empty.
my Rspec test is as follow:
describe Contact do
it "creates a valid model" do
contact = Contact.new(
firstname: 'firstname',
lastname: 'lastname',
email: 'email'
)
expect(contact).to be_valid
end
it "is invalid without a firstname" do
contact = Contact.new(firstname: nil)
contact.valid?
expect(contact.errors[:firstname]).to include("can't be blank")
end
My understanding is that this test should not return any failures but it does. Output below:
Failures:
1) Contact is invalid without a firstname
Failure/Error: expect(contact.errors[:firstname]).to include("can't be blank")
expected [] to include "can't be blank"
# ./spec/models/contact_spec.rb:16:in `block (2 levels) in <top (required)>'
Finished in 0.11659 seconds (files took 2.39 seconds to load)
18 examples, 1 failure, 15 pending
Failed examples:
rspec ./spec/models/contact_spec.rb:13 # Contact is invalid without a firstname
If I change the expect statement from ".to" to "not_to" the test passes, so I think I am getting this backwards, any help or explanation is greatly appreciated.
As smathy said in the comments, I forgot to include the validation inside my model. That solved my problem.

Using RSpec to test for correct order of records in a model

I'm new to rails and RSpec and would like some pointers on how to get this test to work.
I want emails to be sorted from newest to oldest and I'm having trouble testing this.
I'm new to Rails and so far I'm having a harder time getting my tests to work then the actual functionality.
Updated
require 'spec_helper'
describe Email do
before do
#email = Email.new(email_address: "user#example.com")
end
subject { #email }
it { should respond_to(:email_address) }
it { should respond_to(:newsletter) }
it { should be_valid }
describe "order" do
#email_newest = Email.new(email_address: "newest#example.com")
it "should have the right emails in the right order" do
Email.all.should == [#email_newest, #email]
end
end
end
Here is the error I get:
1) Email order should have the right emails in the right order
Failure/Error: Email.all.should == [#email_newest, #email]
expected: [nil, #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[nil,
- #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
+[]
# ./spec/models/email_spec.rb:32:in `block (3 levels) in <top (required)>'
In your code:
it "should have the right emails in the right order" do
Email.should == [#email_newest, #email]
end
You are setting the expectation that the Email model should be equal to the array of emails.
Email is a class. You can't just expect the class to be equal to an array. All emails can be found by using all method on class Email.
You must set the expectation for two arrays to be equal.
it "should have the right emails in the right order" do
Email.order('created_at desc').all.should == [#email_newest, #email]
end
It should work like this.
For newer version of RSpec:
let(:emails) { ... }
it 'returns emails in correct order' do
expect(emails).to eq(['1', '2', '3'])
end

Tests are not passing

I'm using RSpec for tests and I don't know how to get this to green.
In this case, I have a model called "PartType" that holds an attribute called "quotation".
The value for quotation comes from a form, so it will be a string.
To demonstrate you can go to console and type:
(1..1000).includes?("50") # false
but..
(1..1000).includes?(50) # true
And this value can have decimals. So I needed to do a "type_cast".
I have this on my PartTypemodel:
before_validation :fix_quotation, :if => :quotation_changed?
protected
def fix_quotation
self[:quotation] = quotation_before_type_cast.tr(' $, ' , '.' )
end
This are working as expected BUT when go to tests, it fails.
Here is my part_type_spec.rb:
require 'spec_helper'
describe PartType do
before(:each) do
#attr = { :title => "Silver", :quotation => 100 }
end
it "should create a instance given a valid attributes" do
PartType.create!(#attr)
end
it "should accept null value for quotation" do
PartType.new(#attr.merge(:quotation => nil)).should be_valid
end
it "should accept 0 value for quotation" do
PartType.new(#attr.merge(:quotation => 0)).should be_valid
end
end
And finally the failing tests:
Failures:
1) PartType should create a instance given a valid attributes
Failure/Error: PartType.create!(#attr)
NoMethodError:
undefined method tr' for 100:Fixnum
# ./app/models/part_type.rb:7:infix_quotation'
# ./spec/models/part_type_spec.rb:10:in `block (2 levels) in '
2) PartType should accept 0 value for quotation
Failure/Error: PartType.new(#attr.merge(:quotation => 0)).should be_valid
NoMethodError:
undefined method tr' for 0:Fixnum
# ./app/models/part_type.rb:7:infix_quotation'
# ./spec/models/part_type_spec.rb:18:in `block (2 levels) in '
Finished in 0.06089 seconds
3 examples, 2 failures
Your include? snippets are wrong, I got false in the first, true in the second.
before_validation is executed and quotation_before_type_cast is expected to be a String but it is a Fixnum. Change 100 to '100' and 0 to '0'.

Resources