Rails RR Framework: multiple calls for instance_of - ruby-on-rails

I would like write RSpec for my controller using RR.
I wrote following code:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe RegistrationController do
it "should work" do
#deploy and approve are member functions
stub.instance_of(Registration).approve { true }
stub.instance_of(Registration).deploy { true }
post :register
end
end
However RR stubs only deploy method when still calls original approve method.
What syntax should I use to stub both method calls for all instances of Registration class?
UPDATE:
I achivied desired result with [Mocha]
Registration.any_instance.stubs(:deploy).returns(true)
Registration.any_instance.stubs(:approve).returns(true)

It would appear the behavior you describe is actually a bug:
http://github.com/btakita/rr/issues#issue/17

as far as I know, the RSpec mocks don't allow you to do that. Are you sure, that you need to stub all instances? I usually follow this pattern:
describe RegistrationController do
before(:each) do
#registration = mock_model(Registration, :approve => true, :deploy => true)
Registration.stub!(:find => #registration)
# now each call to Registration.find will return my mocked object
end
it "should work" do
post :register
reponse.should be_success
end
it "should call approve" do
#registration.should_receive(:approve).once.and_return(true)
post :register
end
# etc
end
By stubbing the find method of the Registration class you control, what object gets returned in the spec.

Related

RSpec: Stub controller method in request spec

I'm writing an RSpec request spec, which looks roughly like (somewhat shortened for brevity):
describe 'Items', type: :request do
describe 'GET /items' do
before do
allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)
get '/items'
#parsed_body = JSON.parse(response.body)
end
it 'includes all of the items' do
expect(#parsed_body).to include(item_1)
expect(#parsed_body).to include(item_2)
end
end
end
The controller looks like:
class ItemsController < ApplicationController
before_action :doorkeeper_authorize!
def index
render(json: current_user.items)
end
end
As you can see, I'm trying to stub doorkeeper's current_user method.
The tests currently pass and the controller works as expected. My question is about the line:
allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)
I wrote this line based on the answers in How to stub ApplicationController method in request spec, and it works. However, the RSpec docs call it a "code smell" and rubocop-rspec complains, "RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of".
One alternative would be to get a reference to the controller and use instance_double(), but I'm not sure how to get a reference to the controller from a request spec.
How should I write this test avoid code smells / legacy testing approaches?
You're supposed to be on vacation.
I think the right way is to avoid stubbing as much as you can in a request spec, doorkeeper needs a token to authorize so I'd do something like:
describe 'Items', type: :request do
describe 'GET /items' do
let(:application) { FactoryBot.create :oauth_application }
let(:user) { FactoryBot.create :user }
let(:token) { FactoryBot.create :access_token, application: application, resource_owner_id: user.id }
before do
get '/items', access_token: token.token
#parsed_body = JSON.parse(response.body)
end
it 'includes all of the items' do
expect(#parsed_body).to include(item_1)
expect(#parsed_body).to include(item_2)
end
end
end
Here are some examples of what those factories might look like.
Lastly, nice SO points!
have you thought not to mock current_user at all?
if you write a test helper to sign in a user before your request spec, current_user will be populate automatically as if it was a real user. The code would look like this:
before do
sign_in user
get '/items'
#parsed_body = JSON.parse(response.body)
end
if you are using devise gem for authentication it has a nice written wiki page about that here.
This approach is also recommended here by #dhh

Mocking and stubbing in testing

I've recently learned how to stub in rspec and found that some benefits of it are we can decouple the code (eg. controller and model), more efficient test execution (eg. stubbing database call).
However I figured that if we stub, the code can be tightly tied to a particular implementation which therefore sacrifice the way we refactor the code later.
Example:
UsersController
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
User.create(name: params[:name])
end
end
Controller spec
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
By doing that didn't I just limit the implementation to only using User.create? So later if I change the code my test will fail even though the purpose of both code is the same which is to save the new user to database
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new
#user.name = params[:name]
#user.save!
end
end
Whereas if I test the controller without stubbing, I can create a real record and later check against the record in the database. As long as the controller is able to save the user Like so
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
post :create, :name => "abc"
user = User.first
expect(user.name).to eql("abc")
end
end
end
Really sorry if the codes don't look right or have errors, I didn't check the code but you get my point.
So my question is, can we mock/stub without having to be tied to a particular implementation? If so, would you please throw me an example in rspec
You should use mocking and stubbing to simulate services external to the code, which it uses, but you are not interested in them running in your test.
For example, say your code is using the twitter gem:
status = client.status(my_client)
In your test, you don't really want your code to go to twitter API and get your bogus client's status! Instead you stub that method:
expect(client).to receive(:status).with(my_client).and_return("this is my status!")
Now you can safely check your code, with deterministic, short running results!
This is one use case where stubs and mocks are useful, there are more. Of course, like any other tool, they may be abused, and cause pain later on.
Internally create calls save and new
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
So possibly your second test would cover both cases.
It is not straight forward to write tests which are implementation independent. That's why integration tests have a lot of value and are better suited than unit tests for testing the behavior of the application.
In the code you're presented, you're not exactly mocking or stubbing. Let's take a look at the first spec:
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
Here, you're testing that User received the 'create' message. You're right that there's something wrong with this test because it's going to break if you change the implementation of the controllers 'create' action, which defeats the purpose of testing. Tests should be flexible to change and not a hinderance.
What you want to do is not test implementation, but side effects. What is the controller 'create' action supposed to do? It's supposed to create a user. Here's how I would test it
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect { post :create, name: 'abc' }.to change(User, :count).by(1)
end
end
end
As for mocking and stubbing, I try to stay away from too much stubbing. I think it's super useful when you're trying to test conditionals. Here's an example:
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
flash[:success] = 'User created'
redirect_to root_path
else
flash[:error] = 'Something went wrong'
render 'new'
end
end
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it "renders new if didn't save" do
User.any_instance.stub(:save).and_return(false)
post :create, name: 'abc'
expect(response).to render_template('new')
end
end
end
Here I'm stubbing out 'save' and returning 'false' so I can test what's supposed to happen if the user fails to save.
Also, the other answers were correct in saying that you want to stub out external services so you don't call on their API every time you're running your test suite.

Unable to stub helper method with rspec

I am trying to stub a method on a helper that is defined in my controller. For example:
class ApplicationController < ActionController::Base
def current_user
#current_user ||= authenticated_user_method
end
helper_method :current_user
end
module SomeHelper
def do_something
current_user.call_a_method
end
end
In my Rspec:
describe SomeHelper
it "why cant i stub a helper method?!" do
helper.stub!(:current_user).and_return(#user)
helper.respond_to?(:current_user).should be_true # Fails
helper.do_something # Fails 'no method current_user'
end
end
In spec/support/authentication.rb
module RspecAuthentication
def sign_in(user)
controller.stub!(:current_user).and_return(user)
controller.stub!(:authenticate!).and_return(true)
helper.stub(:current_user).and_return(user) if respond_to?(:helper)
end
end
RSpec.configure do |config|
config.include RspecAuthentication, :type => :controller
config.include RspecAuthentication, :type => :view
config.include RspecAuthentication, :type => :helper
end
I asked a similar question here, but settled on a work around. This strange behavior has creeped up again and I would like to understand why this doesnt work.
UPDATE: I have found that calling controller.stub!(:current_user).and_return(#user) before helper.stub!(...) is what is causing this behavior. This is easy enough to fix in spec/support/authentication.rb, but is this a bug in Rspec? I dont see why it would be expected to not be able to stub a method on a helper if it was already stubbed on a controller.
Update to Matthew Ratzloff's answer: You don't need the instance object and stub! has been deprecated
it "why can't I stub a helper method?!" do
helper.stub(:current_user) { user }
expect(helper.do_something).to eq 'something'
end
Edit. The RSpec 3 way to stub! would be:
allow(helper).to receive(:current_user) { user }
See: https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/
In RSpec 3.5 RSpec, it seems like helper is no longer accessible from an it block. (It will give you the following message:
helper is not available from within an example (e.g. an it block) or from constructs that run in the scope of an example (e.g. before, let, etc). It is only available on an example group (e.g. a describe or context block).
(I can't seem to find any documentation on this change, this is all knowledge gained experimentally).
The key to solving this is knowing that helper methods are instance methods, and that for your own helper methods it's easy to do this:
allow_any_instance_of( SomeHelper ).to receive(:current_user).and_return(user)
This is what finally worked for me
Footnotes/Credit Where Credit Due:
Super Props to a blog entry by Johnny Ji about their struggles stubbing helper/instance methods
Try this, it worked for me:
describe SomeHelper
before :each do
#helper = Object.new.extend SomeHelper
end
it "why cant i stub a helper method?!" do
#helper.stub!(:current_user).and_return(#user)
# ...
end
end
The first part is based on this reply by the author of RSpec, and the second part is based on this Stack Overflow answer.
Rspec 3
user = double(image: urlurl)
allow(helper).to receive(:current_user).and_return(user)
expect(helper.get_user_header).to eq("/uploads/user/1/logo.png")
This worked for me in the case of RSpec 3:
let(:user) { create :user }
helper do
def current_user; end
end
before do
allow(helper).to receive(:current_user).and_return user
end
As of RSpec 3.10, this technique will work:
before do
without_partial_double_verification {
allow(view).to receive(:current_user).and_return(user)
}
end
The without_partial_double_verification wrapper is needed to avoid a MockExpectationError unless you have that turned off globally.

Rspec - Accessing Sorcery methods/variables

Trying to write some tests for code I've already written, with a view to extending my code using test-driven development.
I have a controller whose index action calls a 'user_info' method, which just collects together some instance variables relying on Sorcery's current_user variable. For example:
def user_info
#current_A = current_user.a
#current_B = current_user.b
end
def index
user_info
// rest of the method goes here
end
I started writing some tests using rspec, just to get a feel for testing this code base. My controller spec is very basic and looks like this:
describe MyController do
describe "GET 'index'" do
get 'index'
response.should be_success
end
end
However, I get the following error when I try to run this spec:
NoMethodError: undefined method 'a' for false:FalseClass
First of all, how do I get my spec to recognize the Sorcery method current_user? And, out of curiosity, why is current_user being flagged as an instance of FalseClass? If it's not calling the Sorcery method, (and I haven't defined current_user anywhere else in my code), should it not appear as nil?
To use Sorcery test helpers you need the following lines in your spec_helper.rb.
The following needs to be in the Rspec.configure block:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
After you have this in place you can use the Sorcery test helpers. For a Controller test you would add the following to your test.
#user = either a fixture or a factory to define the user
login_user
If you don't want to specify #user you can pass an argument.
login_user(fixture or factory definition)
Once you login the current_user should be available to your tests.
logout_user is also available.
See the Sorcery Wiki for information on setting up a user fixture to work with the login_user helper.
Richard, the problem is likely that you don't have a current_user.
To do that, you need to simulate the login process.
You can do that with a controller spec, but I don't have a good example here. I was writing specs on existing code, like you, and it made sense to use request specs instead.
I also don't have one for Sorcery (I should!!) and I am here using Capybara for filling in forms,. Still, here is how my spec looked:
(Here :account is the same as :user would be)
context "when logged in" do
before :each do
#account = Factory.create(:account)
#current_game = Factory(:game_stat)
visit login_path
fill_in 'Username or Email Address', :with => #account.email
fill_in 'Password', :with => #account.password
click_button('Log in')
end
So factories are another matter, mine looked like this:
Factory.define :account do |f|
f.sequence(:username) { |n| "ecj#{n}" }
f.sequence(:email) { |n| "ecj#{n}#edjones.com" }
f.password "secret"
f.password_confirmation {|u| u.password }
end
You don't have to use factories, but you do need to get that session and current_user established.
On important bit is to ensure the user is activated after creation if you're using the :user_activation submodule of Sorcery.
So, if you're using the fabrication gem, that would look like,
Fabricator(:properly_activated_user, :from => :user) do
after_create { |user| user.activate! }
end
As #nmott mentioned you need to do two things:
1) Register text helper methods using:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
2) In your example access current_user through controller.current_user like that:
login_user(user)
expect(controller.current_user).to be_present

RSpec Rails Login Filter

I recently switched started using rspec-rails(2.6.1) with my Rails(3.0.8) app. I'm used to Test::Unit, and I can't seem to get a filter working for my test methods. I like to keep things as DRY as possible, so I'd like to set up a filter that I can call on any test method that will login as an Authlogic user before the test method is called. I tried accomplishing this by using an RSpec filter in spec_helper.rb:
config.before(:each, :login_as_admin => true) do
post "/user_sessions/create", :user_session => {:username => "admin", :password => "admin"}
end
Then I use it in the corresponding test method(in this case spec/controllers/admin_controller_spec.rb):
require 'spec_helper'
describe AdminController do
describe "GET index" do
it("gives a 200 response when visited as an admin", :login_as_admin => true) do
get :index
response.code.should eq("200")
end
end
end
However, I get this error when I run rspec spec:
Failures:
1) AdminController GET index gives a 200 response when visited as an admin
Failure/Error: Unable to find matching line from backtrace
RuntimeError:
#routes is nil: make sure you set it in your test's setup method.
Blech. Can I only send one HTTP request per test? I also tried stubbing out my authenticate_admin method(inside the config.before block), without any luck.
Unfortunately, there is no way at the moment to do what you're trying to do in a globally defined before hook. The reason is that before hooks are executed in the order in which they get registered, and those declared in RSpec.configure are registered before the one that rspec-rails registers internally to set up the controller, request, response, etc.
Also, this has been reported to https://github.com/rspec/rspec-rails/issues/391.
You should use shulda's macrons. To use shoulda modify your spec_helper.rb
RSpec.configure do |config|
config.include Clearance::Shoulda::Helpers
end
And then can setup filter in controller spec like
require 'spec_helper'
describe AdminController do
fixture :users
before(:each) do
sign_in_as users(:your_user)
end
describe "GET index" do
it("gives a 200 response when visited as an admin", :login_as_admin => true) do
get :index
response.code.should eq("200")
end
end
end

Resources