I am writing a Rspec for update method of a controller which updates the record in a table T with a given id ID. Here in spec I want to add a case that checks whether any record with ID exists in the table T, and if it does not exist, it should give error like following But being new to RSpec I could not complete it.
it "should give error when record with the given id does not exist" do
pending "case yet to be handled"
end
So if anyone helps me in writing this spec.
You should pass the parameter id and use the correct HTTP verb to send request to update action. i.e
it "should give error when record with the given id does not exist" do
put :update, id: <invalid_id>
# you expectations here
end
How I learned to test my Rails applications, Part 4: Controller spe has some example and more explanation on controller tests.
You could do Model.find and check if this record doesn't exist.
it "should give error when record with the given id does not exist" do
expect { Model.find(<your ID>) }.should raise_error(ActiveRecord::RecordNotFound::Error)
end
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.
I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if an User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
expect {
post :create, invitation: attributes_for(:member, email: #users.first.email)
}.to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: expect {
You must pass an argument rather than a block to use the provided matcher (include "valentin#parisian.org"), or the matcher must implement `supports_block_expectations?`.
# ./spec/controllers/users/invitations_controller_spec.rb:17:in `block (4 levels) in <top (required)>'
I am not surprised by the error, because the Test doesn't feel right to me. I just can't quite figure out how to test for this without writing code in my controller#action.
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
The error you are getting is a syntax error, nothing related to whatever your action is supposed to do.
The code you have there it is being interpreted as you are passing a block ({}) to the expect method.
I'd change it to something like
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
Assuming that the response of the create action returns the email as plain text, which seems weird to me.
Also note that I have email directly passed to the post since you mentioned you were expecting it in params[:email] but by the test you wrote seems like you were expecting it in params[:invitation][:email].
Change that part if that is the case.
I'm doing a controller spec in Rails 4, and I'm wanting to test the attributes of a record created by a controller action. How do I find the newly created record?
For example, what could I do instead of
it 'Marks a new user as pending' do
post :create, params
# I don't want to use the following line
user = User.last
expect(user).to be_pending
end
The Rails guides only briefly talks about controller tests, where it mentions testing that Article.count changes by 1, but not how to get a new ActiveRecord model.
The question Find the newest record in Rails 3 is about Rails 3.
I'm reluctant to use User.last, because the default sorting may be by something other than creation date.
If a controller has an instance variable named #user, then we can access it in RSpec by assigns(:user).
It is not a good idea to compare records or objects in controller tests. In a controller create test, you should test the correct redirection and the changing of the record.
You can easily compare your objects in a model test, because you can easily track your record.
Still, you can access the created record from a test if your action has a variable that holds the record like
In controller
def create
#user = #assign user
end
In Test
assigns(:user).name.should eq "Name" # If it has a name attribute
assigns(:user).pending.should be_true # I don't know how you implemented pending
You can take a look at this article
I am new to ruby on rails. I am getting an undefined method error when I run rspec on comment_spec.rb
1) after_save calls 'Post#update_rank' after save
Failure/Error: request.env["HTTP_REFERER"] = '/'
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::AfterSave:0x007fa866ead8d0>
# ./spec/models/vote_spec.rb:45:in `block (2 levels) in <top (required)>'
This is my spec:
require 'rails_helper'
describe Vote do
....
describe 'after_save' do
it "calls 'Post#update_rank' after save" do
request.env["HTTP_REFERER"] = '/'
#user = create(:user)
#post = create(:post, user: #user)
sign_in #user
vote = Vote.new(value:1, post: post)
expect(post). to receive(:update_rank)
vote.save
end
end
Any help that you would have would be greatly appreciated...
I was following the apirails book tutorial chapter 3 here
http://apionrails.icalialabs.com/book/chapter_three
I was receiving the same error and DrPositron's solution worked for me, all green again. Just needed to add ":type => :controller" on my block like so:
describe Api::V1::UsersController, :type => :controller do
end
Hope this helps someone
OK here's the deal.
Vote is a model, i suppose.
You are writing a test for that model.
There's a difference between model tests ("the domain logic is doing what its supposed to") and feature/integration tests ("the application is behaving the way its supposed to").
The request variable is associated with feature or controller tests.
So what's wrong?
You are not logging in users in model tests, just check if the update_rank method is being called on save, thats it.
No user-interaction jazz in model tests.
Hope that helps!
Cheers
Jan
So Louis, just to expand on Jan's response:
You appear to be writing a model spec. The purpose of a model spec is simply to test how your model classes work, and that behavior is testable without having to pay any attention to the application logic around signing in, making "requests" to particular controllers, or visiting particular pages.
You're essentially just testing a couple related Ruby classes. For this, we don't need to think about the whole app -- just the classes we're testing.
As a consequence, RSpec doesn't make certain methods available in the spec/models directory -- you're not supposed to think about requests or authentication in these tests.
It looks like your test is simply designed to make sure that when you create a vote for a post, it updates that post's rank (or, specifically, call's that post's update_rank method). To do that, you don't need to create a user, or sign a user in, or pay any attention to the request (what request would we be referring to? We're just testing this as if in Rails console, with no HTTP request involved).
So you could basically remove the first four lines of your test -- apart from the line creating your post, and the post's user if it's necessary (if the post model validates the presence of a user). Don't sign a user in -- we're just testing a Ruby class. There's no concept of a website to sign into in this test.
Then, as a last thing to take care of to get your spec to pass, make sure to refer to the post you create by the right name. Right now, you're creating a post and assigning it to the #post variable, but then you're referring to just post later on. post doesn't exist; just #post. You'll have to pick one variable name and stick with it.
Also, if you are using RSpec 3, file type inference is now disabled by default and must be opted in as described here. If you're new to RSpec, a quick overview of the canonical directory structure is here.
For example, for a controller spec for RelationshipsController, insert , :type => :controller as such:
describe RelationshipsController, :type => :controller do
#spec
end
I am trying to test a controller method with the following code:
it "should set an approved_at date and email the campaign's client" do
#campaign = Campaign.create(valid_attributes)
post :approve, id: #campaign.id.to_s
#campaign.reload
#campaign.approved_at.should_not be(nil)
end
However, when I run this test, I get the following error:
Failure/Error: #campaign.reload
ActiveRecord::RecordNotFound:
Couldn't find Campaign without an ID
When I run the analagous lines in the rails console, the reload works and the value is set as I need it to be. Why isn't reload working for me when I run the code in an rspec test?
I solved the problem by switching to FactoryGirl:
#campaign = FactoryGirl.create(:pending_approval_campaign)
#campaign.approved_at.should be(nil)
post :approve, id: #campaign.id.to_s
#campaign.reload
#campaign.approved_at.should_not be(nil)
That works as intended
Two possible places for errors.
object creation. i.e.#campaign = Campaign.create(valid_attributes) Your object may not be created correctly. I suggest you to use create! instead of create in the test so that any error will be thrown.
Controller. When controller expect to find the object with an integer id, you feed it a string. That may also be the problem. I suggest you not to convert the id into string. If for GET, you can do that though not necessary. If for POST, converting to string is wrong.
I would run a test to ensure a Campaign record is actually being created:
#campaign = Campaign.create(valid_attributes)
puts #campaign.id
.reload is the first place in your code that a nil #campaign would flag an error (since you can call .to_s on a nil object)