rspec: How to stub an instance method called by constructor?
Here's the commit which adds the feature to rspec - This was on May 25 2008. With this you can do
A.any_instance.stub(do_something: 23)
However, the latest gem version of rspec (1.1.11, October 2008) doesn't have this patch in it.
This ticket states that they yanked it out for maintenance reasons, and an alternative solution hasn't yet been provided.
Doesn't look like you can do it at this point. You'll have to hack the class manually using alias_method or somesuch.
How to check calls to a method called from constructor in rspec?
The new syntax for doing this is as follows:
allow_any_instance_of(ExampleClass).to receive(:example_method).and_return("Example")
expect_any_instance_of(ExampleClass).to receive(:example_method).and_return("Example")
See the docs
RSpec - mock a private class method call inside a request spec?
You're mocking on the class, which is how you mock you "static" class-level methods. For example, if your method was def self.foo
and you called it via MyClass.foo
, then allow(MyClass)
is the way to go.
Your method is not a class-level method, it's an instance method. You invoke it by first creating an instead of MyClass
and then calling the method on that instance. You need to use allow_any_instance_of
to mock the method for all future instances of the class:
allow_any_instance_of(MyClass).to receive(....)
How to stub a method that is called in initialize method
One of the following lines should be present/run in your test before you instantiate a Company
object i.e. before you do Company.new
.
allow_any_instance_of(Company).to receive(:navigate_to_search_result){ true }
or
allow_any_instance_of(Company).to receive(:navigate_to_search_result).and_return(true)
Stub method called from constructor when using Fabrication
This is why I don't like ActiveRecord callbacks -- because if you want to have nothing to do with a callback (because, say, you're making a call to an external service inside the callback) you still have to be concerned about stubbing it out. Yes you could stub out methods inside the callback, but it's the same problem, and actually it's a bit worse because now you are concerned about something inside a method you want nothing to do with.
As usual there are multiple options here.
One option which I've used a lot in the past is, add a condition to your callback that turns it off by default. So your Post class could look like:
class Post
before_save :sync_with_store, :if => :syncing_with_store?
def syncing_with_store?; @syncing_with_store; end
attr_writer :syncing_with_store
def sync_with_store
# make an HTTP request or something
end
end
Now wherever you really want to call the callback (perhaps in your controller or wherever), you can set post.syncing_with_store = true
before you call post.save
.
The downside to this approach is, it's something that you (and other devs working with you) have to keep in mind, and it's not really obvious that you have to do this. On the other hand, if you forget to do this, nothing bad happens.
Another option is to use a fake class. Say you have a Post that pushes its data to an external data store on save. You could extract the code that does the pushing to a separate class (e.g. Pusher) which would be accessible at Post.pusher_service
. By default, though, this would be set to a fake Pusher class that responds to the same interface but does nothing. So like:
class Post
class << self
attr_accessor :pusher_service
end
self.pusher_service = FakePostPusher
before_save :sync_with_store
def sync_with_store
self.class.pusher_service.run(self)
end
end
class FakePostPusher
def self.run(post)
new(post).run
end
def initialize(post)
@post = post
end
def run
# do nothing
end
end
class PostPusher < FakePostPusher
def run
# actually make the HTTP request or whatever
end
end
In your production environment file, you'd set Post.pusher_service = Pusher
. In individual tests or test cases, you'd make a subclass of Post -- let(:klass) { Class.new(Post) }
-- and set klass.pusher_service = Pusher
(that way you don't permanently set it and affect future tests).
The third approach, which I have been experimenting with, is this: simply don't use ActiveRecord callbacks. This is something I picked up from Gary Bernhardt's screencasts (which, by the way, are pretty amazing). Instead, define a service class that wraps the act of creating a post. Something like:
class PostCreator
def self.run(attrs={})
new(attrs).run
end
def initialize(attrs={})
@post = Post.new(attrs)
end
def run
if @post.save
make_http_request
return true
else
return false
end
end
def make_http_request
# ...
end
end
This way PostCreator.run(attrs)
is the de facto way of creating a post instead of going through Post. Now to test saves within Post, there's no need to stub out callbacks. If you want to test the PostCreator process, there's no magic going on, you can easily stub out whichever methods you want or test them independently. (You could argue that stubbing out methods here is the same as stubbing out AR callbacks, but I think it's more explicit what's going on.) Obviously this only handles post creation, but you could do the same for post updating too.
Anyway, different ideas, pick your poison.
Stubbing when an object's constructor builds another object
You could write something like B.should_receive(:new).with({:foo => 'whatever'})
.
Personally, I avoid stubbing/mocking and would rather test for behavior; the fact that a new B
is created with a given set of options is implementation dependent and I wouldn't test that directly.
How to stub class instantiated inside tested class in rspec
Reaching for DI is always a good idea (https://stackoverflow.com/a/51401376/299774) but there are sometimes reasons you can't so it, so here's another way to stub it without changing the "production" code.
1. expect_any_instance_of
it 'uploads to google cloud' do
expect_any_instance_of(Google::Storage).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
In case you just want to test that the method calls the method on any such objects.
2. bit more elaborated setup
In case you want to control or set up more expectations, you can do this
it 'uploads to google cloud' do
the_double = instance_double(Google::Storage)
expect(Google::Storage).to receive(:new).and_return(the_double)
# + optional `.with` in case you wanna assert stuff passed to the constructor
expect(the_double).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
Again - Dependency Injection is clearer, and you should aim for it. This is presented as another possibility.
RSpec: stub method call for external object inside method
You have to stub the class method new
of ExternalThing
and make it return the mock:
it "should raise an Exception when passed a bad argument" do
a = A.new
external_obj = mock('external_obj')
ExternalThing.stub(:new) { external_obj }
external_obj.should_receive(:ext_method).and_raise(Exception)
expect { a.method_one("bad") }.to raise_exception
end
Note that this solution is deprecated in rspec 3. For solution not deprecated in rspec 3 see rspec 3 - stub a class method.
Related Topics
Getting Only New Mail from an Imap Server
Change the Context/Binding Inside a Block in Ruby
Should One Use Dashes or Underscores When Naming a Gem with More Than One Word
In Ruby What Does "=>" Mean and How Does It Work
Cannot Load Such File -- Readline (Loaderror) When Running Heroku Create --Stack Cedar
Get Absolute (Base) Url in Sinatra
How to Get My MAChine's Ip Address from Ruby Without Leveraging from Other Ip Address
Replacing the 'Auto_Link' Method in Ruby on Rails 3.1
Ruby on Rails: Devise, Want to Add Invite Code
Rails. How to Store Time of Day (For Schedule)
What Is '$:.Unshift File.Dirname(_File_)' Doing
How to Unfreeze an Object in Ruby
Why Does Accessing a Ssl Site with Mechanize on Windows Fail, But on MAC Work
Rails: Copying Attributes from an Object to Another Using the "Attributes" Method
Error Installing Debugger: Failed to Build Gem Native Extension with Ruby-1.9.3-P362