Rspec: Stub method that is in the controller
Solution 1
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.
Solution 2
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
Solution 3
If you are testing controller, you can access controller directly:
controller.stub(:message) { 'this is the value to return' }
Solution 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
Solution 5
You can stub a controller
method:
allow(controller).to receive(...).with(...).and_return(...)
shoujo_sm
Updated on July 09, 2022Comments
-
shoujo_sm almost 2 years
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 almost 11 yearsHi, 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.
-
jurglic almost 11 yearsCorrect - 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 almost 11 yearsYou 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...: blog.codeclimate.com/blog/2013/07/23/…
-
ryan2johnson9 over 8 years
allow_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return('companyid')
for Rspec3 -
wacha over 7 yearsif
FbCompanyVerifier#verify
performs an HTTP request (or any other side effect you want to avoid during test), how do you test to prevent it? usingallow_any_instance_of
onFbCompanyVerifier
? -
Volte about 7 yearsThis 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.