unexpected message :body with (no args) when using a double

1,386 views
Skip to first unread message

belgoros

unread,
Oct 23, 2018, 10:15:26 AM10/23/18
to rspec
I have a weird behaviour when running a spec separately passes bu t fails when running all the tests.

Here is the spec to test a service:

require 'rails_helper'


RSpec.describe SportsService do
  let
(:user) { create(:user)}


  describe
'#call' do
    it
'raises an error if the request is failed' do
      service
= described_class.new(user: user)
      allow
(service).to receive(:execute_request).with(anything).and_return(failed_response)


      expect
{ service.call }.to raise_error ExceptionHandler::AuthenticationError
   
end


    it
'send successful response' do
      service
= described_class.new(user: user)
      allow
(service).to receive(:execute_request).with(anything).and_return(successful_response)
      sports_values_to_check
= service.call.map {|sport| {id: sport.id, label: sport.label}}
      expect
(sports_values_to_check).to include(id: 1, label: 'sport-1')
   
end
 
end
end


def failed_response
 
double(:response, code: 100)
end


def successful_response
 
double(:response, code: 200, body: '[{"sport_id": 1, "label": "sport-1"}, {"sport_id": 2, "label": "sport-2"}]')
end


The failing example is "send successful response" that fails with a message:

 1) SportsService#call send successful response

     Failure/Error: create_sports(response.body)

       #<Double :response> received unexpected message :body with (no args)

     # ./app/services/sports_service.rb:17:in `call'

     # ./spec/services/sports_service_spec.rb:17:in `block (3 levels) in <main>'



Why RSpec does not find body defined on my double ? Thank you.

belgoros

unread,
Oct 23, 2018, 10:55:35 AM10/23/18
to rspec
I think I have an idea.
I'm using services and declare them in an initializer created in  config/initilizers/service_provider.rb as follows:

class ServiceProvider
 
@services = {}


 
def self.register(key, klass)
   
return false if @services.key?(key) && !Rails.env.test?
   
@services[key] = klass
 
end


 
def self.get(key)
   
@services[key]
 
end


 
def self.[](key)
   
get(key)
 
end


 
def self.finished_loading
   
@services.freeze unless Rails.env.test?
 
end
end


... #other services being declared here as well
ServiceProvider.register :sports_service, SportsService


ServiceProvider.finished_loading


And in sports_controller I'm getting the service like that:

module V1
 
class SportsController < ApplicationController


   
def index
      json_response sports_service
.call
   
end


   
private


   
def sports_service_class
     
@sports_service_class ||= ServiceProvider.get(:sports_service)
   
end


   
def sports_service
     
@sports_service ||= sports_service_class.new(user: current_user)
   
end
 
end
end


I think I'll have to create and register for such a test a dummy SportsService directly in my rspec file testing the service, somethig like that (spec/services/sports_service_spec):

require 'rails_helper'


SuccessfulUserInfoService = Struct.new(:user, keyword_init: true) do
  include
ServiceClient


 
def call
   
true
 
end
end



RSpec.describe SportsService do
  let
(:user) { create(:user)}


  describe
'#call' do

    after
(:each) do
     
ServiceProvider.register(:sports_service, SportsService) # putting back the original service
   
end
...

This is just an idea, have not tested it yet. May be I'm testing the wrong thing ? I'd like to test the result returned by service's #call method







belgoros

unread,
Oct 23, 2018, 10:58:58 AM10/23/18
to rspec
I forgot to put the rest of the example (see the dummy service initialization):

it 'sends a successful response with sports data' do
     
ServiceProvider.register(:sports_service, SuccessfulSportsService)
      service
= ServiceProvider.get(:sports_service)
      allow
(service).to receive(:execute_request).with(anything).and_return(successful_response)
     
      service_data
= service.call
      sports_values_to_check
= service_data.map {|sport| {id: sport.id, label: sport.label}}

Jon Rowe

unread,
Oct 23, 2018, 3:52:38 PM10/23/18
to rs...@googlegroups.com
Its looks like your doubles are some how leaking between examples, I’d wager its something to do with your service injector but I’m unsure without a proper example.

Cheers
Jon Rowe
---------------------------

belgoros

unread,
Oct 24, 2018, 3:04:51 AM10/24/18
to rspec


On Tuesday, 23 October 2018 21:52:38 UTC+2, Jon Rowe wrote:
Its looks like your doubles are some how leaking between examples, I’d wager its something to do with your service injector but I’m unsure without a proper example.

Cheers
Jon Rowe

I think the problem comes from #successful_response method, - I checked and discovered that I had 2 ones in different specs, both are declared outside of main RSpec.describe block like that:

#spec_one_spec.rb

RSpec.describe 'SpecOne' do
...# some examples
end

def successful_response
  double(:response, code: 200)
end

#spec_two_spec.rb

RSpec.describe 'SpecTwo' do
...# some examples
end

def successful_response
  double(:response, code: 200, body: '[{"sport_id": 1, "label": "sport-1"}, {"sport_id": 2, "label": "sport-2"}]')
end

What is the rule of thumb to declare such a kind of helper methods ? Should we put them inside the main RSpec.describe block, outside, make them private ?

Cheers

belgoros

unread,
Oct 24, 2018, 3:13:39 AM10/24/18
to rspec


On Wednesday, 24 October 2018 09:04:51 UTC+2, belgoros wrote:


On Tuesday, 23 October 2018 21:52:38 UTC+2, Jon Rowe wrote:
Its looks like your doubles are some how leaking between examples, I’d wager its something to do with your service injector but I’m unsure without a proper example.

Cheers
Jon Rowe

I think the problem comes from #successful_response method, - I checked and discovered that I had 2 ones in different specs, both are declared outside of main RSpec.describe block like that:

#spec_one_spec.rb

RSpec.describe 'SpecOne' do
...# some examples
end

def successful_response
  double(:response, code: 200)
end

#spec_two_spec.rb

RSpec.describe 'SpecTwo' do
...# some examples
end

def successful_response
  double(:response, code: 200, body: '[{"sport_id": 1, "label": "sport-1"}, {"sport_id": 2, "label": "sport-2"}]')
end

What is the rule of thumb to declare such a kind of helper methods ? Should we put them inside the main RSpec.describe block, outside, make them private ?

Cheers

I moved the above methods inside the main RSpec.describe block and all the tests passed. So should we always keep them there or there is another best practice ?
Thank you. 

belgoros

unread,
Oct 24, 2018, 3:31:35 AM10/24/18
to rspec
I re-opened the Effective Testing with RSpec-3 book by Myron Marston & Ian Dees, and on page 11 you can already find the explanation:

Each example group is a Ruby class, which means that we can define methods on it. Right after the describe line, add the following code....

def sandwich 

  Sandwich.new('delicious', [])

end 


So I refactored all the examples to fix that. Hope this helps.

Jon Rowe

unread,
Oct 24, 2018, 4:20:56 AM10/24/18
to rs...@googlegroups.com
Ah yes absolutely that was the cause. 

When you define methods outside of an RSpec describe (which creates a class) you are defining methods on `main`. So essentially you overwrote your method definition when you loaded all the specs and its just luck which one was active.

Glad you solved it!

Cheers
Jon Rowe
---------------------------
Reply all
Reply to author
Forward
0 new messages