Gia Phat Ha Gia Phat Ha - 4 months ago 31
Ruby Question

ruby rspec mocks test function receiving a hash

I am toying with the mailgun API, all functionalities seem to work now and I want to test them using rspec mocks.

I made some json fixtures under the rspec/fixtures folder, each has a json representing the expected result when I call a particular function. I also made a small helper:

module TestHelpers
def self.get_json(filename:)
JSON.parse File.read(filename)
end
end


What I want to test is this function:

def self.get_messages_for(email:)
sent_emails = []
delivered_events = get_events_for(email: email)

# :Accept => "message/rfc2822" will help us to get the raw MIME
delivered_events.each do |event|
response = RestClient::Request.execute method: :get ,
url: event["storage"]["url"],
user: "api", password:"#{configuration.api_key}",
Accept: "message/rfc2822"
sent_emails.push(JSON.parse(response))
end
sent_emails
end


Which uses this helper to fetch events:

def self.get_events_for(email:, event_type: "delivered")
delivered_to_target = []
response = RestClient.get "https://api:#{configuration.api_key}"\
"@api.mailgun.net/v3/#{configuration.api_domain}/events",
:params => {
:"event" => event_type
}

all_delivered = JSON.parse(response)["items"]

all_delivered.each do |delivered|
if (delivered.has_key?("recipients") and delivered["recipients"].include?(email)) or
(delivered.has_key?("recipient") and delivered["recipient"].include?(email))
delivered_to_target.push(delivered)
end
end
delivered_to_target
end


And here in my spec I have:

it 'can get the list of previously sent emails to an email address' do

allow(StudySoup).to receive(:get_events_for).with({email: email}) {
Array(TestHelpers::get_json(filename: 'spec/fixtures/events.json'))
}

allow(RestClient::Request).to receive(:execute).with(any_args){
TestHelpers::get_json(filename: 'spec/fixtures/messages.json')
}

expect(StudySoup.get_messages_for(email: email)["subject"]).not_to be nil
end


However, when I tried to run rspec, it always has the following fail trace:

1) StudySoup can get the list of previously sent emails to an email address
Failure/Error: url: event["storage"]["url"],

TypeError:
no implicit conversion of String into Integer
# ./lib/StudySoup.rb:51:in `[]'
# ./lib/StudySoup.rb:51:in `block in get_messages_for'
# ./lib/StudySoup.rb:49:in `each'
# ./lib/StudySoup.rb:49:in `get_messages_for'
# ./spec/StudySoup_spec.rb:86:in `block (2 levels) in <top (required)>'


I thought I stubbed out the
RestClient::Request.execute
method so it should work but it didn't. Any ideas on how I can correctly test this function? I tried to put many params matcher like anything(), hash_including(:key => value)... but it still didn't work.

Answer

You have indeed stubbed out the execute method. This means that rspec fiddles with that class so that attempts to call execute call rspec provided code instead of the original inplementation. In particular, all the arguments to that call are evaluated as normal.

Changing the argument matchers as you have tried changes whether rspec decides the method call matches one of the configured stubs, but can't avoid the fact that evaluating event["storage"]["url"] is raising an exception.

When you stubbed get_events_for you returned an array of arrays instead of an array of hashes: Array calls to_ary or to_a on its arguments which is turning your hash into an array of key value pairs, rather than wrapping the hash in an array as I think you thought it did.