I'm trying to firm up my understanding of why this actually works
My application is just the default scaffold for the purposes of this experiment.
it "creates a record and redirects with success notice" do
Project.any_instance.stubs(:valid?).returns(true) #ensure action behaves as if #project was valid
post :create, :format => :js
assigns(:project).errors.should be_empty #ensures that #project has no errors
flash[:notice].should_not be_nil #ensure a flash notice is set
response.should redirect_to(projects_path) #ensure appropriate redirection
end
end
In the controller the save method is called on #project. Here is where I become unsure. The object will save because the valid? method has been stubbed to return true, even if the object is not valid. But if the object is not valid how can the save be successful?
Rails is also calling your valid? stub internally before saving, so it proceeds with the save as normal because it thinks nothing is wrong.
On the other hand if you had, say, a UNIQUE index on a field in a MySQL database, a duplicate row there could still prevent the record from being saved, even though you stubbed valid?. Two possible layers of validation.
Related
I am trying to build an RSpec test spec for my model: Logo that will ensure that only a singular record can be saved to the database. When I utilize the .build method for the second call to build a Logo, my test fails because FactoryBot is able to build out a Logo.
However, if I use the .create method for the second Logo entry in FactoryBot I receive an error for the test because my model raises an error, as instructed, based upon my model's method for the :only_one_row method.
How can I make this work using RSpec and FactoryBot?
Here is the code I have tried, unsuccessfully:
# app/models/logo.rb
class Logo < ApplicationRecord
before_create :only_one_row
private
def only_one_row
raise "You can only have one logo file for this website application" if Logo.count > 0
end
end
# spec/factories/logos.rb
FactoryBot.define do
factory :logo do
image { File.open(File.join(Rails.root, 'spec', 'fixtures', 'example_image.jpg')) }
end
end
# spec/logo_spec.rb
require 'rails_helper'
RSpec.describe Logo, type: :model do
it 'can be created' do
example_logo = FactoryBot.create(:logo)
expect(example_logo).to be_valid
end
it 'can not have more than one record' do
# Ensure there are no logo records in the database before this test is run.
Logo.destroy_all
example_logo_one = FactoryBot.create(:logo)
# This is where the trouble lies...
# If I go with .create method I error with the raised error defined in my model file...
example_logo_two = FactoryBot.create(:logo)
# ... if I go with the .build method I receive an error as the .build method succeeds
# example_logo_two = FactoryBot.build(:logo)
expect(example_logo_two).to_not be_valid
end
end
Your validation here is implemented as a hook, not a validation, which is why the be_valid call will never fail. I want to note, there's no real issue here from a logical perspective -- a hard exception as a sanity check seems acceptable in this situation, since it shouldn't be something the app is trying to do. You could even re-write your test to test for it explicitly:
it 'can not have more than one record' do
# Ensure there are no logo records in the database before this test is run.
Logo.destroy_all
example_logo_one = FactoryBot.create(:logo)
expect { FactoryBot.create(:logo) }.to raise_error(RuntimeError)
end
But, if there's a possibility the app might try it and you want a better user experience, you can build this as a validation. The tricky part there is that the validation looks different for an unsaved Logo (we need to make sure there are no other saved Logos, period) versus an existing one (we just need to validate that we're the only one). We can make it one single check just by making sure that there are no Logos out there that aren't this one:
class Logo < ApplicationRecord
validate do |logo|
if Logo.first && Logo.first != logo
logo.errors.add(:base, "You can only have one logo file for this website application")
end
end
end
This validation will allow the first logo to save, but should immediately know that the second logo is invalid, passing your original spec.
When I utilize the .build method for the second call to build a Logo, my test fails because FactoryBot is able to build out a Logo.
That is correct, build does not save the object.
However, if I use the .create method for the second Logo entry in FactoryBot I receive an error for the test because my model raises an error, as instructed, based upon my model's method for the :only_one_row method.
Catch the exception with an expect block and the raise_error matcher.
context 'with one Logo already saved' do
let!(:logo) { create(:logo) }
it 'will not allow another' do
expect {
create(:logo)
}.to raise_error("You can only have one logo file for this website application")
end
end
Note this must hard code the exception message into the test. If the message changes, the test will fail. You could test for RuntimeError, but any RuntimeError would pass the test.
To avoid this, create a subclass of RuntimeError, raise that, and test for that specific exception.
class Logo < ApplicationRecord
...
def only_one_row
raise OnlyOneError if Logo.count > 0
end
class OnlyOneError < RuntimeError
MESSAGE = "You can only have one logo file for this website application".freeze
def initialize(msg = MESSAGE)
super
end
end
end
Then you can test for that exception.
expect {
create(:logo)
}.to raise_error(Logo::OnlyOneError)
Note that Logo.destroy_all should be unnecessary if you have your tests and test database set up correct. Each test example should start with a clean, empty database.
Two things here:
If your whole application only ever allows a single logo at all (and not, say, a single logo per company, per user or whatever), then I don't think there's a reason to put it in the database. Instead, simply put it in the filesystem and be done with it.
If there is a good reason to have it in the database despite my previous comment and you really want to make sure that there's only ever one logo, I would very much recommend to set this constraint on a database level. The two ways that come to mind is to revoke INSERT privileges for the relevant table or to define a trigger that prevents INSERT queries if the table already has a record.
This approach is critical because it's easily forgotten that 1) validations can be purposefully or accidentally circumvented (save(validate: false), update_column etc.) and 2) the database can be accessed by clients other than your app (such as another app, the database's own console tool etc.). If you want to ensure data integrity, you have to do such elemental things on a database level.
How can you test the presence of a callback in your model, specifically one that's triggered by creating a record, such as after_create or after_commit on: :create?
Here's an example callback with the (empty) method that it calls.
# app/models/inbound_email.rb
class InboundEmail < ActiveRecord::Base
after_commit :notify_if_spam, on: :create
def notify_if_spam; end
end
Here's the pending spec, using RSpec 3.
# spec/models/inbound_email_spec.rb
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created"
end
end
Using a message expectation to test that the method is called seems like the way to go.
For example:
expect(FactoryGirl.create(:inbound_email)).to receive(:notify_if_spam)
But that doesn't work. Another way is to test that when a record is created, something inside the called method happens (e.g. email sent, message logged). That implies that the method did get called and therefore the callback is present. However, I find that a sloppy solution since you're really testing something else (e.g. email sent, message logged) so I'm not looking for solutions like that.
I think Frederick Cheung is right. This should work. The problem with your example is that the callback has already been called before the expectation has been set.
describe InboundEmail do
describe "#notify_if_spam" do
it "is called after new record is created" do
ie = FactoryGirl.build(:inbound_email)
expect(ie).to receive(:notify_if_spam)
ie.save!
end
end
end
I'm newbie with rspec and I'm facing some problems with it. Could someone help me?
I have a controller action responsible for deactivate an user. I'm trying to cover it with rspec tests, but the result is not what I'm waiting for.
Controller:
def deactivate
#user = User.find(params[:id])
if !#user.nil?
#user.update_attribute(:active, false)
redirect_to users_url
end
end
Controller Spec
describe "PUT #deactivate" do
describe "with valid parameters" do
before (:each) do
#user = mock_model(User, :id => 100, :login => "login", :password => "password123",
:email => "email#gmail.com", :active => true)
User.should_receive(:find).with("100").and_return(#user)
end
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.active.should eq false
end
end
end
The test result:
1) UsersController PUT #deactivate with valid parameters should deactivate an user
Failure/Error: #user.active.should eq false
expected: false
got: true
(compared using ==)
So, I don't understand why the active attribute stills true when it should be false. Any ideas ?
Thanks!
You appear to be stubbing the update_attribute method unnecessarily. Try removing that line and see what happens.
I look for this for a long time, update_column can always work no matter you use let or build
Your expectation is "wrong".
Let's see what happens when your spec it "should deactivate an user" is executed:
#user.stub!(:update_attribute).with(:active, false).and_return(true) modifies the existing mock model, so it has an update_attribute which, when called with arguments :active and false
will return true
will keep track that this call has happened (that's what mocks do)
(and, unlike a real User object, will do nothing else)
put :deactivate, :id => "100" calls the real deactivate in your Controller
Your Controller calls User.find. But you've mocked that class method, which will return the mock object #user instead of searching for the actual user with that id.
Your Controller calls #user.update_attribute. But because of step 3 above, #user here is the mock object, too. Its update_attributes method is the one from step 1. As we've seen above, it will return true, keep track that this call happened and do nothing else. Which means it will not change #user's active attribute, so that stays true.
Changing active when update_attribute is called is functionality of objects of the actual User class, but no such object came into play while running your spec. Because this functionality is inherited from ActiveRecord, you don't have to test it. Instead just test that the update_attribute has been received by the mock object:
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.should have_received(:update_attribute).with(:active, false)
end
(I'm guessing about the old should syntax here, based on how it's done with the newer expect syntax.)
To mock or not?
If you do want to test the combined functionality of your controller with the actual User implementation, do not mock User or its objects. Instead test from the browser perspective with a request spec. (It might make sense to do that additionally, even if you want the isolated tests for only controller (with model mocked) and for only model (which probably won't require doubles, except maybe for other models).
Can you try this:
describe "should deactivate an user" do
before do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
end
it { #user.active.should eq false }
end
when you are mocking the call to update_attribute, how is the model going to change?
if you are a beginner: DONT use stubs and mocks!
first get a general knowledge in testing, THEN expand your knowledge to mocks and stubs.
I have the following
it 'should assign a new profile to user' do
get :new
assigns(:user_profile).should ==(Profile.new)
end
But it's not working. I've tried 'eql?' and 'equal?' respectively. How do I compare it in order to know if the contents of #user_profile is Profile.new?
I used to do a workaround doing a .class of the assigned variable, checking if it's Profile but I want to stop with these bad practices.
Thanks.
The problem here is that Object.new invoked twice by design creates two different objects, which are not equal.
1.9.2p318 :001 > Object.new == Object.new
=> false
One thing you can do here is
let(:profile){ Profile.new }
it 'should assign a new profile to user' do
Profile.should_receive(:new).and_return profile
get :new
assigns(:user_profile).should eq profile
end
Now you're not actually creating a new profile when the controller action is invoked, but you are still testing that Profile is receiving new, and you're testing that the return value of that method is being assigned by the controller to #user_profile.
When I run a post in my Rails functional test
setup do
post :create, :user => Factory.attributes_for(:user)
end
and it fails, I don't get any feedback as to why. I know that it fails because my assertion to make sure that there's one additional record in the database fails.
I tried to do
setup do
post :create, :user => Factory.attributes_for(:user)
assert_valid #controller.object
end
but object is a protected method.
How can I examine the errors on the model object that results from the post call?
I'm using Shoulda and Factory Girl, but I suspect that doesn't matter.
Add the following assertion:
assert_nil assigns(:user).errors
Which will fail if there were errors saving your object (perhaps a validation), and show you the value of the errors object.
I'm using rails 3.2.13, and it seems like assert_nil doesn't work properly as stated in the previous answer.
This is what worked for me:
assert_empty assigns(:user).errors
I believe this is because even a successful "save" call returns an ActiveRecord:Errors object containing with an Empty hash of "messages" so you could also do this:
assert_empty assigns(:user).errors.messages