Expect method call and proxy to original method with RSpec - ruby-on-rails

I want to discover with BDD missing :include params for ActiveRecord::Base.find method. So my idea is to have in spec something like this:
ActiveRecord::Base.should_receive(:find).once.and_proxy_to_original_method
parent = SomeClass.find 34
parent.child.should be_loaded
parent.other_children.should be_loaded
If #child or #other_children associations are not eager loaded, expectation should fail with something like:
"Expected ActiveRecord::Base.find to be invoked once but it was invoked 2 more times with following args: 1. ...; 2. ..."
Does anyone know if there's some matcher that works like this or how to make this.
Thanks

I think I had the same problem here. In your particular case I would do this which I find quite clean.
original_method = ActiveRecord::Base.method(:find)
ActiveRecord::Base.should_receive(:find).once do (*args)
original_method.call(*args)
end
I believe you could extend the Rspec Mocks::MessageExpectation class to include the and_proxy_to_original_method method, shouldn't be too hard, but I haven't looked.

Related

Rspec expect(instance) to receive method not working as expected

#controller file
def update
#payment = Payment.find_by(reference_id: params[:reference_id])
if #payment.update(update_params)
#payment.do_something
end
end
when trying to spec if do_something method was called, by
expect(#payment).to receive(:do_something)
it says
expected: 1 time with any arguments
received: 0 times with any arguments
do_something is in my Payment Class. It is actually being called, but rspec says not.
Any ideas? thanks in advance
Firstly you need to stub the lines in controller in order to expect some code
before do
allow(Payment).to receive(:find_by).and_return(payment)
allow(payment).to receive(:update).and_return(true)
allow(payment).to receive(:do_something)
end
Also, instance variable in controller won't be directly accessible in rspecs.
So, First create a payment object in rspecs using let and use it before block like I did it in above solution
Your #payment in specs is actually a totally different variable, which is part of specs class, not the controller. I may be wrong, but that is my assumption from the parts of code you post - add specs code for more info.
As of the solution, may use 'stub any instance'
Payment.any_instance.stub(:do_something).and_return(:smthing)
A more complicated approach - using doubles

How does this mapper pattern work?

I found RailsCasts episode, and used this logic and code samples for my needs.
But one thing bothers me.
constraint looks like:
constraints(Subdomain.new) do ... end
which uses this code:
class Subdomain
def matches?(request)
....
end
end
end
And it works.
But I don't get two things. First, I do not invoke matches? anywhere, why this method is just executed on initializing Subdomain.new?
Second concern. I don't pass any parameter, but it somehow assigns request argument to actual rack request and it just works.
For example, I didn't like this syntax:
constraints(Subdomain.new) do ... end
so I decided to make it module with method subdomain(request), but as made it module, it started raising wrong number or arguments error (0 for 1).
I found out that method matches? is defined in mapper.rb, may be it is called somewhere backwards in rails, but this way it should be overwritten by my subdomain file? If not, as my matches is class method, how it works without any Subdomain instance to which it is applied?
As I said, everything works fine, but I would like to understand what exactly happens, because I don't like using something that appears david blane magic code to me.
Reading some source code of Rails mapper module didn't give me understanding.
Well, little more of reading source code gave me a clue. I found one more matches?, defined for #constraints
def matches?(req)
#constraints.all? do |constraint|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
end
end
So for every constraint it checks if it responds to matches? and then invokes it with rack request argument.

Rspec is it possible to do something like should_receive(:foo).with(:bar).and_call_original.exacly(1).times?

Is it possible to do something like ServiceObject.should_receive(:foo).with(:bar).and_call_original.exacly(1).times?
My specs look something like this:
it 'should call instance of service object\'s :baz! method' do
ServiceObject.any_instance.should_receive(:baz!).exactly(1).times
end
it 'should call service object\'s :foo method' do
ServiceObject.should_receive(:foo).with(:bar).and_call_original.exacly(1).times
end
If I remove and_call_original the first spec fails. If I comment out the .exacly(1).times in the second spec both specs pass.
Two questions:
Why do these two specs interfere with one another?
Is there a way to call something that means .should_receive(:foo).with(:bar).and_call_original.exacly(1).times?
Thanks in advance!
and_call_original doesn't return the current example so it cannot work.
Instead you can do:
.should_receive(:foo).with(:bar).exacly(1).times.and_call_original

Changing mocha default expects exactly once

I just started using mocha and I find it annoying that when creating a new mock object, mocha expects it to be called exactly once. I have helper methods to generate my mocks and I'm doing something like this
my_mock = mock(HashOfParameters)
All of the parameters might not get called for each test method so it will raise an error:
expected exactly once, not yet invoked
So I figured I needed to do something like this:
my_mock = mock()
HashOfParameters.each do |k, v|
my_mock.expects(k).returns(v).at_least(0)
end
This works but I was wondering if there was an easier way to do this, like changing a default configuration somewhere...
Ok, that was a stupid question... I hadn't took the time to truly understand the difference between a mock and a stub. Here's a good article that shows how it works :
http://martinfowler.com/articles/mocksArentStubs.html
So in my example, I should have been using the stub method instead of mock.

Rails ActiveRecord: Automatically Alias/Append Suffix?

I've got a legacy database with a bunch of idiotically named columns like:
some_field_c
some_other_field_c
a_third_field_c
I would very much like to make a Rails ActiveRecord sub-class that automatically aliases these attributes to their name minus the underscore and "c". However, when I tried:
attributes.each_key do | key |
name = key
alias_attribute key.to_sym, key[0, (key.length -2)].to_sym if key =~ /_c$/
end
in my class definition I got an "undefined local variable or method `attributes'" error. I also tried overwriting these methods:
method_missing
respond_to?
but I kept getting errors with that route too.
So my question (actually questions) is/are:
Is what I'm trying to do even possible?
If so, what's the best way to do it (iterative aliasing or overwriting method missing)?
If it's not too much trouble, a very brief code sample of how to do #2 would be awesome.
Thanks in advance for any answers this post receives.
Your problem is probably that attributes is an instance method and you're doing this in the context of the class. The class method that's closest to what you want is column_names.
you could do something like:
methods.each do |method|
if method.ends_with("_c") then
self.send(:defind_method,method.slice(0,-2)){self.send(method)}
end
end
Hmm... I cooked up this little solution that I thought ended up pretty elegantly...
Not sure if this will work, but I aliased method_missing to still allow active_record to do it's thang:
module ShittyDatabaseMethods
alias :old_method_missing :method_missing
def method_missing(method)
if methods.include?("#{method}_c")
send("#{method}_c".to_sym)
else
old_method_missing(method)
end
end
end
class Test
attr_accessor :test_c
include ShittyDatabaseMethods
end
You may not get to name your module "ShittyDatabaseMethods", but you get the idea ;) Once you define that module and stuff it into lib, you just need to include this module and off you go :D
Would love to hear if this works out for you :)

Resources