How to write Rspec spec for the following query - ruby-on-rails

Hi I have the following query in my controller and I want to write the Rspec spec . I am new to Rspec and I don't know how to write the spec. Kindly help
table1.includes(:table2).where(table1: {id: params[:id]}).includes(:table3)
I also tried looking into mocks and stubs but i don't understand how to use them for a query like this.
Thanks

When faced with these issues, I tend to encapsulate the query in a method. That way, you can stub out the method with data simply and without worrying about data-sanitation.
For example:
def fetch_table1_results(id)
table1.includes(:table2).where(table1: {id: id}).includes(:table3)
end
At this point, you can stub out the method when you need to test things that depend on it:
awesome_model = stub_model(Table1, fetch_table1_results: [1, 2, 'etc']) # You should include models, stubs, or mocks here.
As far as testing the actual method, I'm not sure you need to. There aren't many interesting parts of that method chain. If you wanted to be complete, here are the cases:
Ensure fetch_table1_results calls any instance of Table1.find with id
Ensure fetch_table1_results eager-loads table2 and table3
The way of doing the latter varies, but I'm rather fond (and this won't be a popular opinion) of checking the database query directly. So you could type something like the following:
fetch_table1_results(1).to_sql.should include('JOIN table2')
That, or something similar. I should also note that these tests should be in the model, not the controller.

Related

How do I expect a method to be run with specific ActiveRecord parameters

Using Mocha on Rails 4.2.
I'm testing a method that it should make a call to another method with the correct parameters. These parameters are ActiveRecord objects that it calls up from the database. Here is the key line in my test:
UserMailer.expects(:prompt_champion).with(users(:emma), [[language, 31.days.ago]]).once
Both users(:emma) and language are ActiveRecord objects.
Even though the correct call is made, the test fails because the parameters don't match the expectations. I think this might be because it's a different Ruby object each time a record is pulled up from the database.
I think one way around it is to see what method is being used in my code to pull up the records and stub that method to return mocks, but I don't want to do this because a whole bunch of Records are retrieved then filtered down to get to the right one, mocking all those records would make the test way too complex.
Is there a better way of doing this?
You could use block form of allow/expect.
expect(UserMailer).to receive(:prompt_champion) do |user, date|
expect(user.name).to eq "Emma"
expect(date).to eq 31.days.ago # or whatever
end
Sergio gave the best answer and I accepted it. I discovered the answer independently and found out along the way that I needed to return a mock from the ActionMailer method to make everything work properly.
I think it best to post here my complete test here for the sake of any other hapless adventurer to come this way. I'm using Minitest-Spec.
it 'prompts champions when there have been no edits for over a month' do
language.updated_at = 31.days.ago
language.champion = users(:emma)
language.save
mail = mock()
mail.stubs(:deliver_now).returns(true)
UserMailer.expects(:prompt_champion).with do |user, languages|
_(user.id).must_equal language.champion_id
_(languages.first.first.id).must_equal language.id
end.once.returns(mail)
Language.prompt_champions
end
You could use an RSpec custom matcher and compare expected values in that function.

How to test a method on an ActiveRecord::Relation object in rspec?

How do I test a method available only to an ActiveRecord relation proxy class in rspec? Like for example sum which would look something like #collection.sum(:attribute)
Here is what I'm trying to do:
#invoice = stub_model(Invoice)
#line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: #invoice})
#invoice.stub(:line_items).and_return([#line_item])
#invoice.line_items.sum(:cost).should eq(10)
This doesn't work because #invoice.line_items returns a regular array that doesn't define sum in the same way as an ActiveRecord::Relation object does.
Any help is greatly appreciated.
I'm not sure which Rails you are on so I'll use Rails 4.0.x for this example; the principle still holds for Rails 3.x.
TL;DR: You don't want to take this route.
Consider not stubbing model specs
Consider adding domain specific APIs
You are rapidly heading down the road of over mocking/stubbing. I have been down this road, it does not lead to fun. Part of all of this comes down to violating the Law of Demeter. Part of it comes down to using the Rails APIs instead of creating your own domain APIs.
When you request an relation collection from an ActiveRecord model it does not return an Array as you are aware. In Rails 4.0.x, with a has_many association, the class which is returned is: ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Model.
Issue #1: Stubbing the wrong return value
Here your return type is an Array. While the actual return type is the ActiveRecord_Associations_CollectionProxy_Model. In stub/mock land, this isn't necessarily a bad thing. However, if you intend to use other calls on the object returned by the stub they need to match the same API contracts. Otherwise, you're not stubbing the same behavior.
In this case, the sum method defined on the AR association proxy actually executes SQL when it runs. The sum method defined on Array is patched in via Active Support. The Array#sum behavior is fundamentally different:
def sum(identity = 0, &block)
if block_given?
map(&block).sum(identity)
else
inject { |sum, element| sum + element } || identity
end
end
As you can see, it sums the elements, not the sum of the requested attribute.
Issue #2: Asserting on your stub'd object
The other main problem you have, is you are attempting to spec that you're stub returns what you stubbed. This doesn't make sense. The point of a stub is to return a canned answer. It's not to assert on how it behaves.
What you wrote isn't fundamentally different from:
invoice = stub_model(Invoice)
line_item = stub_model(LineItem, {quantity: 1, cost: 10.00, invoice: invoice})
invoice.stub(:line_items).and_return([line_item])
invoice.line_items.should eq([line_item])
Unless this is supposed to be a sanity check, it adds no real value to your specs.
Suggestions
I'm not sure what type of spec you are writing here. If this is a more traditional unit test or an acceptance test, then I probably wouldn't stub anything. There isn't necessarily anything wrong with hitting a database at times, especially when the thing you are testing is how you interact with it; which is really what you are doing here.
Another thing you can do is start to use this to create your own specific domain model APIs. All this really means is defining interfaces on objects that make sense for your domain, which may or may not be backed by a DB or other resource.
For example, take your invoice.line_items.sum(:cost).should eq(10), this is clearly testing the Rails AR API. In domain terms it means nothing really. However, invoice.subtotal probably means a lot more to your domain:
# app/models/invoice.rb
class Invoice < ActiveRecord::Base
def subtotal
line_items.sum(:cost)
end
end
# spec/models/invoice_spec.rb
# These are unit specs on the model, which directly works with the DB
# it probably doesn't make sense to stub things here
describe Invoice do
specify "the subtotal is the sum of all line item cost" do
invoice = create(:invoice)
3.times do |i|
cost = (i + 1) * 2
invoice.line_items.create(cost: cost)
end
expect(invoice.subtotal).to eq 12
end
end
Now later, when you use Invoice in some other part of your code, you can easily stub this if you need to:
# spec/helpers/invoice_helper_spec.rb
describe InvoiceHelper do
context "requesting the formatted subtotal" do
it "returns US dollars to two decimal places" do
invoice = double(Invoice, subtotal: 1012)
assign(:invoice, invoice)
expect(helper.subtotal_in_dollars).to eq "$10.12"
end
end
end
So when it is ok to stub model specs? Well, that's really a judgement call, and will vary from person to person, and code base to code base. However, just because something is in app/models doesn't mean it has to be an ActiveRecord model. In those cases, it's potentially fine to stub domain APIs on collaborators.
EDIT: create vs build
In the example above I used create(:invoice) and invoice.line_items.create(cost: cost). However, if you are concerned about DB slowness, you probably could just as easily use build(:invoice) and invoice.line_items.build(cost: cost).
Be aware that my use of create(:invoice) and build(:invoice) here is in reference to generic "factories", not a reference to a specific gem. You could simply use Model.create and Model.new in their place. Additionally, the line_items.create and line_items.build are provided by AR and have nothing to do with any factory gems.

Alternatives to find_by for ROR

I'm working through Michael Hartel's rails tutorial, on 6.3 and need alternative code for the user_spec model. The code that he has is:
let(:found_user) { User.find_by(email: #user.email) }
It looks like I can use where, but unsure of the correct syntax. I tried several variations of the following:
let(:found_user) { User.where(:email => "#user.email")}
I'm sure this is a pretty easy answer, but cant quite get it.
let(:found_user){User.where(email: #user.email).first}
or
let(:found_user){User.find_by_email(#user.email)}
That first one that uses where returns a collection of users that match the where clauses, which is why you would need that .first (It doesn't execute the sql until you grab the records with something like .all, .first, or .each).
I would say it's not the best practice to execute database commands in a unit test though. What are you testing specifically? Is there a reason you need the user to be saved in the database and can't just do something like:
let(:user){User.new(email: 'some email')}
ActiveRecord::Base#find_by is effectively where(options).first, but that's a whole extra call that you needn't make.
Rails also provides mildly deprecated "magic" find_by_<attribute>[and_<attribute>] methods which used method_missing to parse out what was meant based on the name of the method. While the framework does provide these, I caution against using them as they are necessarily slower than "native" methods, and are more resistant to refactoring.
I would recommend sticking with find_by for the general case, and would try to avoid hitting the database in specs and tests.
The factory_girl gem provides a method to create a stubbed version of the class which quacks like a record returned from the database by answering true for persisted? and providing an id.
Alternatively, you can just build a new record without saving it: User.new(attribute: value, ...) and run your tests on that:
it "does some things" do
user = User.new(attributes)
# make user do some things
expect(things).to have_happened
end

Set an expectation without mocking anything

Using MiniTest::Spec and Mocha:
describe "#devices" do
it "scopes the devices by the provided :ip_list" do
ips = 'fast tests ftw!'
ds = DeviceSearch.new ip_list: ips
Device.expects(:scope_by_ip_list).once.with(ips)
ds.devices
end
end
When I make the code work correctly, this test will fail, because calling Device.expects(:scope_by_ip_list) also stubs Device.scope_by_ip_list, and since I don't specify a .returns(Devices.scoped) or some such, it stubs out the method with nil. So, in my code which properly scopes a list of devices and then does further operations, the further operations blow up.
I don't want to have to specify a .returns parameter, though, because I totally don't care what it returns. I don't want to stub the method at all! I just want to set up an expectation on it, and leave it functioning just the way it is.
Is there a way to do that?
(To me, it seems very awkward to say something like Device.expects(:foo).returns('bar')—when I say that Model expects method, I'm not saying to stub that method! We can say Device.stubs(:foo), if we want to stub it.)
The behavior is intended and can't be changed. Look at the following post to see how it can be circumwented:
rspec 2: detect call to method but still have it perform its function

RSpec's stub_chain returns stub even when it does not include full chain of scopes

I ran into an issue with my RSpec specifications and ActiveRecord scopes today.
I have a controller that executes code similar to this
#customers = Customer.active.with_counts.order('name asc')
‘active’ and ‘with_counts’ are scopes on the Customer model.
I used to have a specification that used stub_chain like this:
Customer.stub_chain(:with_counts, :order).with('name asc') { [mock_customer] }
I did not expect this to succeed since the controller included the ‘active’ scope but to my surprise it succeeded without any problems.
I guess that the ‘active’ scope is called on the real Customer class and returns something that the stub_chain sort of connects to and it therefore appears to work.
How can I write my specification to avoid this kind of issue when I chain ActiveRecord scopes?
Note that the actual call in the controller will not always used both scopes - it is dependent on filtering so just wrapping everything in a new method is not a way that I am keen to go.

Resources