What is the purpose of stubbing an HTTP request (e.g. using the WebMock gem)? - ruby

As a precursor FYI, I'm a budding developer. I'm trying to write a test for an http POST method for a Ruby gem. From what I can understand, when you stub an http response, for instance with the Ruby WebMock gem, you're basically telling it what to post and then artificially telling it what to respond with. For example, here is the code I'm trying to test:
## githubrepo.rb
module Githubrepo
include HTTParty
def self.create(attributes)
post = HTTParty.post(
'https://api.github.com/user/repos',
:headers => {
'User-Agent' => 'Githubrepo',
'Content-Type' => 'application/json',
'Accept' => 'application/json'
},
:basic_auth => {
:username => attributes[:username],
:password => attributes[:password]
},
:body => {
'name' => attributes[:repository],
'description' => attributes[:description]
}.to_json
)
Githubrepo.parse_response_from(post, attributes[:wants_ssh])
end
My RSpec test fails when I write:
Githubrepo.create(:repository => 'test', :username => 'test_user', :password => '1234')
because it makes a real HTTP request. It recommends I do the following instead:
stub_request(:post, "https://test_user:test_password#api.github.com/user/repos").
with(:body => "{\"name\":\"test_repo\",\"description\":null}",
:headers => {'Accept'=>'application/json', 'Content-Type'=>'application/json', 'User-Agent'=>'Githubrepo'}).
to_return(:status => 200, :body => "", :headers => {})
But to me, this seems like it's pointless since it's basically telling what to send and what to respond with. I can edit the URL to say "https://bananas#git-banana.banana" and the header to say Content-type => 'Rumplestilskin' and RSpec is ok with that. How am I supposed to integrate this into testing the functionality of the create method I specified above? Or if anything, can somebody point me to a solid beginner guide or blog to help me with this question? The Ruby gem READMEs seem to assume the user knows a thing or two already about this and I don't.

As Steve mentions in a comment, the point of this type of test is not to test the external API but instead that your code to handle and parse the response is correct.
As discussed in the comments to this question, check out the VCR gem for "recording" API responses to make sure your code processes them correctly: https://github.com/vcr/vcr

Related

Ruby Restclient different double point or astrophobe and is order important

i am new to ruby RestClient. i have search many example of this restclient and in docruby. For me is important while using ruby restclient, to get the data very fast.
But some are not answers, this is why i would like to question to you all.
i am working on this ruby restclient example Code:
restClient = RestClient::Request.new(
:method => :get,
:url => url,
:verify_ssl => true, #required using https
:content_type => :json,
:accept => :json,
:headers => {
:Authorization => "Bearer #{token}",
}
)
result = restClient.execute()
My first question is what is different of using double point and astrophobe?
restClient = RestClient::Request.new(
:method => :get,
:method => 'get',
...
)
Second question is, is sequences/order in Code important like first url then method or method then url and so on?
restClient = RestClient::Request.new(
:url => :url,
:method => :get,
...
)
#or
restClient = RestClient::Request.new(
:method => :get,
:url => :url,
...
)
third question is, about accept to put in headers. some put accept and content-type in headers and some not, what is different?
restClient = RestClient::Request.new(
:content_type => 'application/json',
:accept => 'application/json',
#or
:headers => {
'hello-token' => "Bearer #{token}",
'content_type'=> 'application/json',
'ACCEPT' => 'application/json'
}
)
What is different of using double point and astrophobe?
:get is a Symbol, 'get' is a String.
It depends on the implementation of the gem if the gem is able to process both. Because the RestClient documentation uses a Symbol in its examples I would recommend doing the same.
But actually – at least in its current version - it doesn' make a difference because the gem translate the argument internally into a string anyway (see initialize and normalize_method)
is sequences/order in Code important
In theory, a hash is an unordered data structure. Therefore the order should not be important in this case. But keep in mind that Ruby's implementation of a hash is actually preserving the order in which the keys are inserted when iterating the hash.
Accept headers
I didn't find any example in the gem's documentation in which they used the first version. Did you actually try both versions? I would be surprised when both worked. Therefore I suggest using the header: version.

Stub request if it contains keyword

I want to stub requests from rspec file if it contains the keyword zimpler no matter what is the POST address and the content.
stub_request(:post, "http://www.example.com/").
with(body: "zimpler")
to_return(:status => 200, :body => xml_file, :headers => {})
is there a way to implement this?

http client with custom params

I use this code to make network request:
request = HTTPClient.new()
request.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
request.set_auth(url, terminal['api_login'], terminal['api_password'])
response = request.post(url, request_body, {"Content-Type" => "application/xml", "cache-control" => "no-cache"}).body
But when I tried to implement rspec with stub request two params are added which are every time different on every test env:
stub_request(:post, "http://www.example.com/").
with(:headers => {'Accept'=>'*/*', 'Cache-Control'=>'no-cache', 'Content-Type'=>'application/xml', 'Date'=>'Thu, 08 Mar 2018 14:20:55 GMT', 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.2.2 (2015-04-13))'}).
to_return(:status => 200, :body => "", :headers => {})
How I can configure http client not to send Date and for User-Agent to send something simple like 'Ruby'?
Is it possible to configure these params?
There is a great gem to deal with this kind of situations, it's called Timecop https://github.com/travisjeffery/timecop. Basically, you can freeze time, or move to a specific date. In your case, I think you just need to freeze the time so you can set an arbitrary date in the stub headers.
Timecop.freeze
stub_request(:post, "http://www.example.com/").
with(:headers => {'Accept'=>'*/*', 'Cache-Control'=>'no-cache', 'Content-Type'=>'application/xml', 'Date'=> Time.now, 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.2.2 (2015-04-13))'}).
to_return(:status => 200, :body => "", :headers => {})
# the rest of your test here
end
Also, you can simplify your stub by omitting the headers and using a regex for the url, something like:
stub_request(:post, /.example.*\/).and_return(:status => 200, :body => "", :headers => {})

Can't add email to Campaign Monitor API?

I am trying to create some simple Ruby code to add emails using the Campaign Monitor API. Below is my code.
require 'httparty'
require 'json'
def request
url = 'https://api.createsend.com/api/v3.1/subscribers/MYLISTID.json'
auth = {:username => 'MYAPIKEY', :password => 'x'}
response = HTTParty.post(url,
:basic_auth => auth, :body => {
'EmailAddress' => 'mike#hotmail.com',
'Name' => 'Test',
'Resubscribe' => true,
'RestartSubscriptionBasedAutoresponders' => true
})
puts response
puts response.code
end
request
I can connect with the API. However, when I try to add the email I am getting the following response.
{"Code"=>400, "Message"=>"Failed to deserialize your request.
Please check the documentation and try again.
Fields in error: subscriber"}
400
When I change the request to get instead of put
my response is:
{"Code"=>1, "Message"=>"Invalid Email Address"}
I can't understand what I am doing wrong as I have followed the documentation on the Campaign Monitor API
It looks like you have everything setup correctly, you just need to turn the body of the post into a json string.
response = HTTParty.post(url,
:basic_auth => auth, :body => {
'EmailAddress' => 'mike#hotmail.com',
'Name' => 'Test',
'Resubscribe' => true,
'RestartSubscriptionBasedAutoresponders' => true
}.to_json)
I'd like to point out that a Campaign Monitor API gem also exists that will do all of that work for you.
Campaign Monitor API Gem

POST JSON data with Curl Multi from ruby

I am using the curb gem to do a Curl Multi post using JSON data. However I am unable to actually get the parameters to get posted and have been unable to figure out how to properly configure the parameters.
urls = [
{
:url => "http://localhost:5000/",
:method => :post,
:headers => {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
:post_fields => {'field1' => 'value1', 'k' => 'j'}
}
]
Curl::Multi.http(urls) do |easy, code, method|
puts "#{easy.body_str.inspect}, #{method.inspect}, #{code.inspect}"
end
=>
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n<p>The browser (or proxy) sent a request that this server could not understand.</p>\n", :post, nil
Do that:
urls = [
{
:url => "http://localhost:5000/",
:method => :post,
:headers => {'Accept' => 'application/json', 'Content-Type' => 'application/json'},
:post_fields => {},
:post_body => {'field1' => 'value1', 'k' => 'j'}.to_json,
}
]
The problem: curb doesn't know that you are sending a JSON data. Curb don't read and interprets the contents of :headers. As you can see here, curb transforms your hash into a string separated by "&", which is the default for a normal (non-json) http data sending (eg.: "field1=value1&k=j"). When the server (Rails) read and interprets the header explicity saying that the data is in JSON format, it tries to decode and the result is the same exception that you get when you do that: JSON.parse("field1=value1&k=j").
To solve this, you need to send "post_fields" as an empty hash, and send your actual data by using "post_body". Also, you need to convert your hash to json manually with to_json.
I don't know if they (the curb project owners) know this problem, but I suggest you to warning them about it.

Resources