I've got a controller test that I'm trying to work through. Basically the controller looks like this:
before_filter :set_up_foo
def set_up_foo
...
#foo = SomeObject.create(params[:some_object_attributes])
...
end
def some_action
# reference #foo
if #foo.nil?
...
else
# this is what I want to test
...
end
end
I don't want to test the innards of the else block, not the before filter. In my case there are a TON of models objects I'd have to mock in order to actually execute the before filter. I have another test for that. I want to make sure the 'side effect' of the before filter is done...in this case that #foo gets initialized to a mock version of it.
Basically I want to do:
controller.foo = mock_foo_object
but this doesn't work. I also tried:
controller.stub!(:foo).and_return(mock_foo_object)
and though I expected (haha) this to work, it didn't. The only real feedback I get is that the #foo object is never set and as a result we got the if, not the else condition.
any help will be greatly appreciated...
So I found this solution: Instead of mocking the accessor, I can pass a block into the stub on the method that has the side effect, to DO that side effect:
controller.stub!(:set_up_foo) { controller.set_instance_variable(:#foo, #mock_foo_object) }
This has the effect of making sure the one side effect I care about actually happens. I tried this and it worked like a charm.
Related
A method replies upon two distinct APIs for geolocation, the second serving as a backup:
def admin_create
#user_object = User.create!(user_params)
set_area(#user_object)
end
def set_area(resource)
do_geocode_lookup(resource)
if !resource.lon
do_alternate_geocode_lookup(resource)
end
end
Finding a data set that returns an empty lon/lat set is challenging (and orthodoxy pushes one to write the test first),
so.. Is there a way to stub the test so that
do_geocode_lookup returns empty lon lat values
do_alternate_geocode_lookup(resource) method gets invoked? and thus tested?
sign_in operator_user
post admin_create_users_url, params: {user: { [...] } }
assert[...]
Using Mocha you can stub out a method so that any instance of an object returns a specified value.
For example....
Account.any_instance.stubs(:reviews_enabled).returns(:true)
It also allows you test that a method was called...
Account.any_instance.expects(:review_started)
The fact you are altering the passed in resource instead of returning something makes things a bit trickier, but I'd be tempted to have do_geocode_lookup return true if it finds something, and false otherwise, to make things a little easier to test.
def set_area(resource)
found = do_geocode_lookup(resource)
unless found
found = do_alternate_geocode_lookup(resource)
end
found
end
Then you could do something like this in your test...
Geocoder.any_instance.stubs(:do_geocode_lookup).returns(:false)
Geocoder.any_instance.expects(:do_alternate_geocode_lookup)
result = sut.set_area(user)
If the expected method isn't called, you get an "expected method wasn't called" error. You can get far more sophisticated with the stubs and expects, but in this case you shouldn't have to.
So, I have a method in a class as follow:
def installation_backlog
Api::Dashboards::InstallationsBacklog.new(operational_district_id, officer_id).backlog_tasks
end
And I want to spec it. So, I just wrote an RSpec test to test it as follow:
it "should call a new instance of InstallationsBacklog with the backlog_tasks method" do
expect_any_instance_of(Api::Dashboards::InstallationsBacklog).to receive(:backlog_tasks)
#installation_officer.installation_backlog # #installation_officer is a new instance of the container class.
end
And this is working.
However, I began to wonder if this was a correct way of doing it. Like: am I sure that even if I stub the wrong ( probably inexistent ) method, and test for it, will it pass or fail?
I tried it, it passed
So, if later, the method name gets changed, there is no way for this test to detect that.
So, here is the question: How can I be sure that a RSpec stubbed method is actually existent in a code?
Here's how I'd set it up. Might help..
let(:backlog) {
double('Backlog', backlog_tasks: [])
}
before do
allow(Api::Dashboards::InstallationsBacklog).to receive(:new).
and_return(backlog)
end
it 'instantiates InstallationBacklog' do
expect(Api::Dashboards::InstallationBacklog).to receive(:new).
with(operational_district_id, officer_id)
#installation_officer.installation_backlog
end
it 'calls backlog_tasks on instance of InstallationBacklog' do
expect(backlog).to receive(:backlog_tasks)
#installation_officer.installation_backlog
end
Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end
Caching is by far the most logic-intensive part of my view code, so I would like to do fragment caching from inside a decorator, however, I cant do it.
When i do this from my decorator:
def cached_name
h.cache do
"a name here"
end
end
I get this:
You have a nil object when you didn't expect it! You might have
expected an instance of Array. The error occurred while evaluating
nil.length
I instantiate my decorator from inside a controller
#presenter = SomePresenter::new
I am using HAML for my views
How can I succesfully cache from inside my decorator, so my view can do stuff like this
= #decorator.cached_logic_heavy_stuff
UPDATE: I have created a git repo showing my issue: https://github.com/houen/presenter_caching
UPDATE: This maybe works - see the repo
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache do
h.concat "i should still not be empty"
end
end
end
I'd suggest using Rails.cache directly might solve your problem; we do the same thing in our decorators with Rails 4.
def cached_name
Rails.cache.fetch(source) do
source.name # etc.
end
end
If you're using Draper, I believe you don't need to explicitly pass the view context. You will likely want to pass a model or collection to your draper present when you instantiate. Examples:
class UserDecorator < Draper::Base
decorates :user
# additional methods
end
# in the controller
#presenter = UserDecorator.new(#user) # for an instance
#presenter = UserDecorator.decorate(#users) # for a collection
I suspect the nil object error you're getting is coming from another method call that's not listed in your code.
As for fragment caching from your decorator, you'll want to use the concat helper method to get this to work inside the decorator:
# your decorator class
def cached_name
h.cache("some_cache_key") do
h.concat "a name here"
end
end
Rails' cache method tries to infer a cache key based on the view that it's being called from. Since you're not actually calling it from a view (but from inside an instance of a decorator class), I expect that it's bombing when trying to build a cache key.
You might try passing a cache key explicitly, via h.cache "your cache key" do. With a full stack trace, you can figure out where it's throwing the exception, and then work around that, as well. Without the full stack trace, it's harder to help you, though.
Edit: Looking at Rails' caching code, I think this might be a deeper issue; it's attempting to get the length of output_buffer, which isn't going to be available outside of your views' contexts (that is, within Draper). You might try adding:
def output_buffer
h.output_buffer
end
But without testing it, I'm thinking it might not work exactly as planned without some more work. This is just a rough guess - I'd be surprised if this is actually the issue, but hopefully it gets you on the right path.
The note in the source there:
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
indicates that this isn't a fully-solved problem, so you may need to do a little digging around in the Rails internals to make this one work.
This works:
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache "some_key10", :expires_in => 10.seconds do
h.concat "i should still not be empty 2"
end
end
end
I am using FactoryGirl and Rspec for testing.
The model sets a foreign key after init if it is nil. Therefore it uses data of another association. But how can I test it?
Normally I would use a factory for creation of this object and use a stub_chain for "self.user.main_address.country_id". But with the creation of this object, after initialize will be invoked. I have no chance to stub it.
after_initialize do
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
Any idea?
Ideally it's better that you test behavior instead of implementation. Test that the foreign key gets set instead of testing that the method gets called.
Although, if you want to test the after_initialize callback here is a way that works.
obj = Model.allocate
obj.should_receive(:method_here)
obj.send(:initialize)
Allocate puts the object in memory but doesn't call initialize. After you set the expectation, then you can call initialize and catch the method call.
Orlando's method works, and here's another which I'd like to add. (Using new rspec 'expect' syntax)
expect_any_instance_of(Model).to receive(:method_here)
Model.new
Another approach is to refactor your code to allow simple unit tests. As is, your code is approaching the upper end of how much code I'd put in a callback:
after_initialize do
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
If it grew much more, I'd extract it to a method and reduce your callback to a method call:
after_initialize :init_country_id
def init_country_id
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
The bonus here is that testing init_country_id becomes just another method unit test at this point...nothing fancy about that.
Now that you've got a unit test on the behavior, you can also test that it gets called, if you're in doubt. (Something as simple as after_initialize :init_country_id does not need invocation testing, IMO)
You can use gem shoulda-callback-matchers to test that your callbacks are actually getting triggered as intended:
it { is_expected.to callback(:init_country_id).before(:initialize) }