18

May I know how to stub method that is in the controller create method? I need to write the spec for this but I got these errors. I need to check the create method in controller must execute validate_fbid method before create a new company record in model.

Error:

1) Companies new company create with valid information#validate_fbid should have correct parameters and return value
 Failure/Error: CompaniesController.create.should_receive(:validates_fbid).with(company)
 NoMethodError:
   undefined method `create' for CompaniesController:Class
 # ./spec/requests/companies_spec.rb:38:in `block (5 levels) in <top (required)>'

  2) Companies new company create with valid information#validate_fbid should fbid validation passed
 Failure/Error: CompaniesController.create.stub(:validates_fbid).and_return('companyid')
 NoMethodError:
   undefined method `create' for CompaniesController:Class
 # ./spec/requests/companies_spec.rb:43:in `block (5 levels) in <top (required)>'

CompaniesController

def create 
company = Company.new(params[:company])
verifyfbid = validate_fbid(company)

if verifyfbid != false
    if company.fbid.downcase == verifyfbid.downcase
        if company.save 
            @message = "New company created."
            redirect_to root_path
        else 
            @message = "Company create attempt failed. Please try again."
            render 'new' 
        end 
    else 
        @message = "Company create attempt failed. Invalid facebook id."
        render 'new' 
    end
else  
    @message = "Company create attempt failed. No such facebook id."
    render 'new'            
    end             
 end 

  private  
  def validate_fbid(company)
   uri = URI("http://graph.facebook.com/" + company.fbid)
   data = Net::HTTP.get(uri)
   username = JSON.parse(data)['username']      
   if username.nil?
    return false 
   else
    "#{username}"
   end
 end

Requests/companies_spec.rb

context "#validate_fbid" do               
        #validate fbid
        let(:company){ Company.new(name:'Example Company', url: 'www.company.com', fbid: 'companyid', desc: 'Company desc' )}

        it "should have correct parameters and return value" do
            CompaniesController.create.should_receive(:validates_fbid).with(company)
                                .and_return('companyid')
        end

        it "should fbid validation passed" do               
            CompaniesController.create.stub(:validates_fbid).and_return('companyid')
            company.fbid.should_not be_nil
            company.fbid.should == 'companyid'
            company.save
            expect { click_button submit }.to change(Company, :count).by(1)
        end                                             
    end    
shoujo_sm
  • 3,173
  • 4
  • 37
  • 60

5 Answers5

25

You don't want to stub the method, when it is the subject of your test case

context "#validate_fbid" do
  #test the function here
  #don't stub
end

when you test the create action in the controller, you can stub "validate_fbid"

describe "post create" do
   ...
   CompaniesController.any_instance.stub(:validates_fbid).and_return('companyid')
   ...
end

Hope it helps.

Henry
  • 1,047
  • 9
  • 8
6

When code is hard to test, it is usually because it is complex.

You should refactor this code this way:

  • move the verification logic into new 'service class' which has a single responsibility of company verification on facebook
  • this will make verification functionality independent of web layer and much easier to test
  • make spec for service class which will test this code in isolation (no controllers)
  • cleanup the controller of logic - you don't want to have logic inside your controllers (rule of thumb: one level of nesting max)
  • spec for controller will be easier as well

The controller code can look something like this:

def create
  company = Company.new(params[:company])
  verified = FbCompanyVerifier.new.verify(company)

  if verified and company.save
    # success logic
  else
    # fail logic
  end
end
TheWebs
  • 12,470
  • 30
  • 107
  • 211
jurglic
  • 3,599
  • 1
  • 17
  • 13
  • Hi, thanks for the response. Does it means I need to create a class FbCompanyVerifier that has 'new' method and 'verify' method too? I am suppose I am not allow to create a class in the controller page so which folder I should create "FbCompanyVerifier"? Thanks for the clarification. I am very new to this. – shoujo_sm Jul 29 '13 at 12:55
  • Correct - a full class with instance method 'verify' and you can skip the 'new' (initialize) method if it does not do anything. Usually this is a best practice so you operate with first-class objects / plain ruby objects. (Some might use class with class methods, which has its' own pros & contras). Since this class is dependent on your model Company, you should put it in somewhere in the app folder 'app/services'. You can put there small single responsibility classes, that contain a method or two, and are independent of the web layer. – jurglic Jul 29 '13 at 15:52
  • You might also check code_climate blog post, which describes similar refactoring - extracting logic code into new class with single responsibility, write simple spec, and then use it as a PORO (plain-old-ruby-object) in the code...: http://blog.codeclimate.com/blog/2013/07/23/testing-code-in-a-rails-initializer/ – jurglic Jul 29 '13 at 15:54
  • if `FbCompanyVerifier#verify` performs an HTTP request (or any other side effect you want to avoid during test), how do you test to prevent it? using `allow_any_instance_of` on `FbCompanyVerifier`? – wacha Dec 08 '16 at 22:01
6

If you are testing controller, you can access controller directly:

controller.stub(:message) { 'this is the value to return' }
Andrew Feng
  • 1,912
  • 17
  • 20
  • This answer works for me because when I stub the method, I know it takes an argument, but I don't know what the argument is. I just want to pass the argument along, so I use a block with an argument to override (stub) the method. – Volte Apr 20 '17 at 21:34
4

Here's the recommended syntax for Rspec 3 (3.3):

  • allow_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return("companyid")

or

  • expect_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return("companyid")

source: https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance

bigtex777
  • 1,150
  • 10
  • 15
1

You can stub a controller method:

allow(controller).to receive(...).with(...).and_return(...)
Kris
  • 19,188
  • 9
  • 91
  • 111