RSpec how to stub open? - ruby

I've been trying to stub open, the open-uri version, and I'm not succeeding.
I've tried doing the following but the request keeps going through:
Kernel.should_receive(:open).and_return("Whatever for now")
I've also tried to do
OpenURI::OpenRead.should_receive(:open).and_return("Whatever for now")
Since I tracked down that was where HTTP requests were made in OpenURI.
Thanks in advance for any suggestions!

Here is what I do
class Gateway
def do_something
open('http://example.com').read
end
end
In my spec i do the following:
describe 'communication' do
it 'should receive valid response from example.com' do
gateway = Gateway.new
gateway.stub_chain(:open, :read).and_return('Remote server response')
gateway.do_something.should == "Remote server response"
end
end

I found a solution here on Stack Overflow after some more time on Google (I can't believe I didn't find this before).
Explanation taken from here and written by Tony Pitluga (not linkable).
If you are calling sleep within the context of an object, you should stub it on the object[...]
The key is, to stub sleep on whatever "self" is in the context where sleep is called.
So I did this and it all worked out:
let(:read) { mock('open') }
it "should return the new log-level when the log level was set successfully" do
read.stub(:read).and_return('log-level set to 1')
kannel.should_receive(:open).and_return(read)
kannel.set_log_level(1).should == 1
end

I'd recommend using something to stub the network instead. I believe the current favorite for doing so is FakeWeb [docs]. You may also be interested in fakeweb-matcher for rspec.
Alas, I think FakeWeb might not work with open(), actually, it stubs Net::HTTP, so I'm not sure if that will work. Any chance of not using open()? :)

Related

How to rspec mock Net::HTTP calls deep in my library?

I'm updating an application that was working with a very old version of RSpec (2.9.0). It works fine with 3.8, but I get a deprecation error about the following code:
response = Net::HTTPOK.new(1.0, "200", "OK")
response.stub(:content_type => 'text/json', :body => contents_raw)
Now, this code is a mock callout to an external API, and it is deep inside my code's libraries. The application I'm testing is a Sinatra app, so I'm using "get" from Rack::Test::Methods to test my app, but then deep inside the app itself is this response.stub
Apparently I should be using "double()" and "allow(object).to receive(...)", but all the examples I've seen are for using double directly in your test's "it" block, which this code is nowhere near. If I actually try to use double I just get a no method error.
WebMocks seems like a very large hammer to just replace this single call.
Maybe the right thing to do is to make a superclass of Net::HTTPOK and pass the response data in wit that superclass's new?
Huh. OK, so the wrapper class method turned out to be far easier than I expected. In my spec file I added:
class HttpWrapper < Net::HTTPOK
def initialize(data, *args)
#data = data
super(*args)
end
def content_type
'text/json'
end
def body
#data
end
end
(Bizarre detail: if I replace every instance of "data" above with "body", it explodes horribly.)
And at the block of code in question I now have:
response = HttpWrapper.new(contents_raw, 1.0, "200", "OK")
And that seems to have done the trick just fine.
If this is a terrible idea for some reason, hopefully someone will let me know. :)
the best way to mock http, use gem which called iswebmock

how to reset expectations on a mocked class method?

Sorry if this is plain simple. i am new to ruby as well as rspec and it seems rspec is a very 'obscure' world (esp when coming from a .net background).
In my 'spec', i have:
before(:each) do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
end
This works fine for all my 'examples', except one where i want it to return false.
expect(File).to receive(:exist?).with("non_existent.yaml").and_return (false)
This obviously fails my test because although "non_existent.yaml" expectation was met, the "dummy.yaml" was not:
(<File (class)>).exist?("dummy.yaml")
expected: 1 time with arguments: ("dummy.yaml")
received: 0 times
So how can i do a 'Reset' on 'File.exist?' (a class method mock) before i setup the new expectation for it? (... "non_existent.yaml"..)
i googled and it yielded:
RSpec::Mocks.proxy_for(your_object).reset
but this gives me:
NoMethodError:
undefined method `proxy_for' for RSpec::Mocks:Module
I could not find anywhere in the documentation that this is how you should do it, and past behaviors goes to show that this solution might also change in the future, but apparently this is how you can currently do it:
RSpec::Mocks.space.proxy_for(your_object).reset
I would follow #BroiSatse's remark, though, and think about re-designing the tests, aiming to move the expectation from the before block. The before block is meant for setup, as you say, and the setup is a very weird place to put expectations.
I'm not sure how you came to this design, but I can suggest two possible alternatives:
If the test is trivial, and will work anyway, you should create one test with this explicit expectation, while stubbing it for the other tests:
before(:each) do
allow(File).to receive(:exist?).with("dummy.yaml").and_return (true)
end
it "asks if file exists" do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
# do the test...
end
If the expectation should run for every test, since what changes in each scenario is the context, you should consider using shared examples:
shared_examples "looking for dummy.yaml" do
it "asks if file exists" do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
# do the test...
end
end
it_behaves_like "looking for dummy.yaml" do
let(:scenario) { "something which sets the context"}
end
You might also want to ask myron if there is a more recommended/documented solution to reset mocked objects...
This worked for me to unmock a specific method from a class:
mock = RSpec::Mocks.space.proxy_for(MyClass)
mock.instance_variable_get(:#method_doubles)[:my_method].reset
Note: Same logic of
RSpec::Mocks.space.proxy_for(MyClass).reset which resets all methods
Expanding on #Uri Agassi's answer and as I answered on another similar question, I found that I could use RSpec::Mocks.space.registered? to check if a method was a mock, and RSpec::Mocks.space.proxy_for(my_mocked_var).reset to reset it's value.
Here is the example I included in my other answer:
Example: Resetting a mocked value
For example, if we wanted to reset this mock back to it's unmocked
default value, we can use the RSpec::Mocks.space.proxy_for helper to
find our mock, then reset it:
# when
# Rails.configuration.action_controller.allow_forgery_protection == false
# and
# allow(Rails.configuration.action_controller).to receive(:allow_forgery_protection).and_return(true)
RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
Rails.configuration.action_controller.allow_forgery_protection
# => true
RSpec::Mocks.space.proxy_for(Rails.configuration.action_controller).reset
Rails.configuration.action_controller.allow_forgery_protection
# => false
Notice however that the even though the mock value has been reset, the
mock remains registered?:
RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
When using "expect_any_instance" I had success using the following method to change the mock (e.g. our example: Putting out a Twitter post and returning a different tweet id)
expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12"))
# post tweet
RSpec::Mocks.space.verify_all
RSpec::Mocks.space.reset_all
expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12346"))
# post another tweet

How to stub a ruby method and conditionally raise an exception based upon state of object

In short, I want to raise an exception via a stubbed method, but only if the object that has the stubbed method has a particular state.
Mail::Message.any_instance.stub(:deliver) do
if to == "notarealemailaddress!##!##"
raise Exception, "SMTP Error"
else
return true
end
end
This doesn't work, because the context inside the stub block is: RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2.
How do I get access to the stubbed object?
using ruby 2, rspec 2.
The actual scenario is I have an app that sounds out thousands of emails in batches and I have code that catches SMTP exceptions, logs the batch, and proceeds. So I want to test sending several batches, where one of the batches in the middle throws an exception.
It looks like this is solved in the latest(currently alpha) version of Rspec v3:
https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643
it "passes the instance as the first arg of the implementation block" do
instance = klass.new
expect { |b|
klass.any_instance.should_receive(:bees).with(:sup, &b)
instance.bees(:sup)
}.to yield_with_args(instance, :sup)
end
I believe you specify the arguments using the with method, so in your case it would be something along the lines of:
Mail::Message.any_instance.stub(:deliver).with(to: "notarealemailaddress!##!##") do
raise Exception, "SMTP Error"
end
There's full documentation here:
https://www.relishapp.com/rspec/rspec-mocks/v/2-3/docs/method-stubs
Ok, here's how you can get this behavior fairly easily without upgrading:
class Rspec::Mocks::MessageExpectation
# pulling in behavior from rspec v3 that I really really really need, ok?
# when upgrading to v3, delete me!
def invoke_with_orig_object(parent_stub, *args, &block)
raise "Delete me. I was just stubbed to pull in behavior from RSpec v3 before it was production ready to fix a bug! But now I see you are using Rspec v3. See this commit: https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643" if RSpec::Version::STRING > "2.99.0.pre"
args.unshift(#method_double.object)
invoke_without_orig_object(parent_stub, *args, &block)
end
alias_method_chain :invoke, :orig_object
end
Drop that at the bottom of your spec file. You'll notice I even add a check to raise an error once RSpec is upgraded. boom!

How to fake a web service with ruby and MiniTest

I'm writing a Rails app to send text messages using the Twilio API:
http://www.twilio.com/docs/api/rest/sending-sms
and to this end, I do:
client = Twilio::REST::Client.new account_sid, auth_token
client.account.sms.messages.create({
# stuff ...
})
That's all good and nice -- however, I don't want my tests to send a bunch of text messages because that would be stupid. So, I'd like to override Twilio::REST::Client.new to give me an object that'll let me call acccount.sms.messages.create in my tests without undue fuss.
I have a solution that works, but feels ugly:
def mock_twilio_service(stub_client)
Twilio::REST::Client.stub :new, stub_client do
yield
end
end
class Recordy
attr_accessor :calls
def initialize
#calls = []
end
def method_missing(method, *args)
ret = self.class.new
#calls << {
method: method,
args: args,
block_given: block_given?,
ret: ret
}
yield if block_given?
ret
end
end
and then in my test:
test "send a text" do
cli = Recordy.new
mock_twilio_service cli do
# ... stuff
end
end
I feel like I'm missing something Super Obvious, but I'm not sure. Am I? Or am I totally barking up the wrong tree? (Yes, I've looked at How do I mock a Class with Ruby? but I don't think it's quite the same...?)
Another idea would be to use WebMock. As your client is making requests to Twilio. You can just stub out the requests. Within the stub you can also define what is returned from the requests and with which parameters it can be called.
And when you set
WebMock.disable_net_connect!
it is sure that no real requests can be made from the test.
This way you don't change any behavior of your test and will not rely on an external API for your tests to pass.
Twilio evangelist here.
We wrote Test Credentials exactly for this scenario. Test Credentials are a special set of credentials (AccountSid and AuthToken) that you can use when you make requests to the Twilio REST API that tell it to basically just go through the motions of making a phone call or sending a text message, but not actually do it (or charge you for it).
You can also use a special set of phone numbers to get Twilio to return specific success or error conditions.
You can find your test credentials in your Twilio dashboard.
Hope that helps.

Generate an HTTP response in Ruby

I'm working on an application that reaches out to a web service. I'd like to develop a proxy class that returns a fake response from the service, so I don't have to constantly be hitting it with requests while I'm developing/testing other parts of the app.
My application is expecting a response generated via Net::HTTP.
response = Net::HTTP.get(URI.parse('http://foo.com'))
case response
when Net::HTTPOK
# do something fun
when Net::HTTPUnauthorized
# you get the idea
How can I manufacture a response object, give it all the right headers, return a body string, etc?
response = ProxyClass.response_object
case response
when Net::HTTPOk
# my app doesn't know it's being lied to
Thanks.
It's actually not that hard to roll your own fake responses directly with Net::HTTP. Here's a simple 200 OK with a cookie header:
def fake_response
net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK")
net_http_resp.add_field 'Set-Cookie', 'Monster'
RestClient::Response.create("Body goes here", net_http_resp, nil)
end
Since few of us are using raw Net::HTTP anymore, the (optional) last line wraps it up as a RestClient::Response, which can then be stubbed into RestClient:
stub(RestClient).post(anything) { fake_response }
I would start with FakeWeb and see if that meets your needs. If it doesn't you can probably gut whatever you need out of the internals and create your own solution.
I know this post is old, but instead of FakeWeb which seems to be largely dead, try webmock. It seems to be more full-featured and very active.
I would look into a mocking library like mocha.
Then you should be able to setup a mock object to help test:
Then following example is from Tim Stephenson's RaddOnline blog, which also includes a more complete tutorial:
def setup
#http_mock = mock('Net::HTTPResponse')
#http_mock .stubs(:code => '200', :message => "OK", :content_type => > "text/html", :body => '<title>Test</title><body>Body of the page</body>')
end
For testing a web service client, we use Sinatra, a lovely little lightweight web framework that lets you get something up and running very quickly and easily. Check out the home page; it has an entire Hello World app in 5 lines of code, and two commands to install and run the whole thing.
I ended up using a Struct.
FakeHttpResponse = Struct.new(:status, :body)
http = FakeHttpResponse.new('success', 'body goes here')
http['status'] # = 'success'
http.body # = 'body goes here'
The drawback is that .status and ['body'] are also valid, but I don't think that matters much.
I would either use FakeWeb as mentioned above, or have my rake test task start a Webrick instance to a little sinatra app which mocks the various test responses you're hoping to see.
You could look into using Rack for this which should allow you to do everything you need.

Resources