Rspec: How to Stub an Instance Method Called by Constructor

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



Leave a reply



Submit