Rspec actions that change the DB - ruby-on-rails

I'm a bit confused with the behavior of rpsec tests involving controller methods that affect the DB. I have seen many examples of rspec tests that involve POST's and DELETE's where people check to see that an object was created or deleted. In most of these tests people are able to just check that the count of the model in the DB has increased or descreased with a tests such as:
delete :clear_photos, :id => #album.id
#album.photos.size.should == 0
or with lambdas:
lambda {delete :destroy, :id => #photo.id}.should change(#album.photos, :size).by(-1)
The syntax isn't perfect in the last example but my point is that in my experience, I have needed to call reload on the object in order for any of these tests to pass, but for some reason, other are able to make them work without explicitly calling reload. Something about calling reload every time I am testing a db create/destroy action seems fishy to me.
Can anyone help me understand what's going on? Thanks!
ACTUAL CODE UPDATE
it "should clear all photos for an album" do
#album = Factory(:album, :photos => [Factory(:photo), Factory(:photo)])
delete :clear, :album_id => #album.id
#album.photo_count.should == 0
end
I get this response:
'PhotosController#clear should clear all photos for an album' FAILED
expected: 0,
got: 2 (using ==)
./spec/controllers/photos_controller_spec.rb:17:
If I reload the #album before calling photo_count though, it works.

I would like to point out that testing a model state in a controller spec is not a very good practice, because it violates the isolation of a unit test. You should instead test if a controller response is appropriate for the current scenario.

Related

Find last created record RSpec test

How could I write a test to find the last created record?
This is the code I want to test:
Post.order(created_at: :desc).first
I'm also using factorybot
If you've called your method 'last_post':
def self.last_post
Post.order(created_at: :desc).first
end
Then in your test:
it 'should return the last post' do
expect(Post.last_post).to eq(Post.last)
end
On another note, the easiest way to write your code is simply
Post.last
And you shouldn't really be testing the outcome of ruby methods (you should be making sure the correct ruby methods are called), so if you did:
def self.last_post
Post.last
end
Then your test might be:
it 'should send the last method to the post class' do
expect(Post).to receive(:last)
Post.last_post
end
You're not testing the outcome of the 'last' method call - just that it gets called.
The accepted answer is incorrect. Simply doing Post.last will order the posts by the ID, not by when they were created.
https://apidock.com/rails/ActiveRecord/FinderMethods/last
If you're using sequential IDs (and ideally you shouldn't be) then obviously this will work, but if not then you'll need to specify the column to sort by. So either:
def self.last_post
order(created_at: :desc).first
end
or:
def self.last_post
order(:created_at).last
end
Personally I'd look to do this as a scope rather than a dedicated method.
scope :last_created -> { order(:created_at).last }
This allows you to create some nice chains with other scopes, such as if you had one to find all posts by a particular user/account, you could then chain this pretty cleanly:
Post.for_user(user).last_created
Sure you can chain methods as well, but if you're dealing with Query interface methods I feel scopes just make more sense, and tend to be cleaner.
If you wanted to test that it returns the correct record, in your test you could do something like:
let!(:last_created_post) { factory_to_create_post }
. . .
it "returns the correct post"
expect(Post.last_post).to eq(last_created_post)
end
If you wanted to have an even better test, you could create a couple records before the last record to verify the method under test is pulling the correct result and not just a result from a singular record.

Rails Functional test functions works separately, but not together. Using cancan, devise, RoR4

I am using RoR4, Cancan(1.5.0) and Devise(3.2.2).
I am using Test:Unit to test my application.
The problem is that if I separate the requests in different functions, it works, but if inside a function I perform two tests, it seems like it evaluates the response of the first request, even after subsequent requests:
This works:
test 'admin cannot delete product that has line_items associated' do
sign_in #admin_user
assert_no_difference('Product.count') do
delete :destroy, id: #prod_to_all
end
assert_redirected_to product_path(#prod_to_all)
end
test 'admin can delete product that has no line_items associated' do
sign_in #admin_user
assert_difference('Product.count', -1) do
delete :destroy, id: products(:prod_to_all_not_ordered)
end
assert_redirected_to products_path
end
If I put them requests together, it fails:
test 'admin cannot delete product that has line_items associated, but can delete one that has no line_items associated' do
sign_in #admin_user
assert_no_difference('Product.count') do
delete :destroy, id: #prod_to_all
end
assert_redirected_to product_path(#prod_to_all)
assert_difference('Product.count', -1) do
delete :destroy, id: products(:prod_to_all_not_ordered)
end
assert_redirected_to products_path
end
Error:
"Product.count" didn't change by -1.
Expected: 5
Actual: 6
My issue is that I have 3 roles: public_user, client, and admin. And to test every function for each role in different functions is a pain. even for simple controllers, it gets bigger than it should, and I hate that solution.
What do you guys suggest? Do I really need to embrace rspec and its contexts or can I get away with test:unit and keep the code DRY?
Besides, it seems to me that something is not so well with test:unit, due to the fact that it doesn't evaluate the second request correctly...is it a bug or something more structural that I am not understanding?
Thank you
I actually like the separate version better. It is cleaner. And I personally like my tests to not be too dry. It makes them more verbose. Which I prefer: Duplication in Tests Is Sometimes good
Rails functional tests are meant to be run with one request per test. If you want to tests multiple requests you can use an integration tests.
It is hard to tell the error without seeing the controller code. It might be that you application is loosing the logged in user during requests. Or that they share state (e.g. instance variables). Debug with pry or some alternate debugger or just plain old puts to see the state of objects.

Is this caused by attr_accessible?

I just lately update my model with attr_accessible fields and suddenly some tests would not work, as i would expect. However, i have a spec like:
context "when user buys a game item" do
let(:inventory) {#user.inventory << Factory(:inventory)}
it "should present an error if the id ..." do
GameItem.stub(:find_by_id).and_return(Factory(:game_item))
#user.inventory.should == 1 # TEST
post :buy, :id => (game_item.id + 1)
flash[:error].should == I18n.t('error.invalid_post')
response.should redirect_to melee_url('Weapon')
end
end
The line #user.inventory.should == 1 is just a check that i made now. The inventory is nil for some reason. Does this happen because of the << operation? I would guess that this is most probable, due to the inventory_id attribute of the User model.
I have to say that attr_accessible generally seems like a hack to me and i kinda don't like it, though i can see why it should be used. Do you think this is the case? If so, how can i stay clear of that check?
let is lazy; it won't call the block unless the variable you're defining is used, and I don't see you accessing inventory anywhere. You access #user.inventory, but that's not the same thing.
Either lose the let definition and just put it in your it block, or make sure you call it first before you make sure it did what it was supposed to.

Stubbing named_scope in an RSpec Controller

I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:
class Customer < ActiveRecord::Base
# ...
named_scope :active_customers, :conditions => { :active => true }
end
and I'm trying to stub it out in my Controller spec:
# spec/customers_controller_spec.rb
describe CustomersController do
before(:each) do
Customer.stub_chain(:active_customers).and_return(#customers = mock([Customer]))
end
it "should retrieve a list of all customers" do
get :index
response.should be_success
Customer.should_receive(:active_customers).and_return(#customers)
end
end
This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have #customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.
I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...
Back to your question, there are two things (or more?) that require spec'ing here:
That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
That the active_customers named_scope does in fact return customers where the active field is true.
I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:
describe CustomersController do
it "should be successful and call Customer#active_customers" do
Customer.should_receive(:active_customers)
get :index
response.should be_success
end
end
In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(#whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).
You should have the array around the mock if you want to test that you receive back an array of Customer records like so:
Customer.stub_chain(:active_customers).and_return(#customers = [mock(Customer)])
stub_chain has worked the best for me.
I have a controller calling
ExerciseLog.this_user(current_user).past.all
And I'm able to stub that like this
ExerciseLog.stub_chain(:this_user,:past).and_return(#exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])

Why did post fail in my Rails functional test?

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

Resources