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.
Related
I've been trying to stub a private module method for the whole day now but with not progress.
Here is a snippet of my application controller class
class ApplicationController < ActionController::Base
include Cesid::Application
end
Cesid > Application.rb
module Cesid
module Application
extend ActiveSupport::Concern
included do
before_action :track_marketing_suite_cesid, only: [:new]
end
private
def track_marketing_suite_cesid
return unless id_token_available?
## #cesid_auth = Auth.new(#id_token)
#cesid_auth = Auth.new(id_token)
return unless #cesid_auth.present? && #cesid_auth.valid?
#cesid_admin = Admin.where(email: #cesid_auth.email).first_or_initialize
end
def id_token_available?
## #id_token.present?
id_token.present?
end
def id_token
#id_token ||= id_token_param
end
def id_token_param
cookies[:id_token]
end
end
end
Now, I'm trying to create a simple unit test for the method
id_token_available?
And I am just trying to set the id_token_param to a random value.
I've tried using this code as stated Is there a way to stub a method of an included module with Rspec?
allow_any_instance_of(Cesid).to receive(:id_token_param).and_return('hello')
but I just get this error
NoMethodError:
undefined method `allow_any_instance_of' for #<RSpec::ExampleGroups::CesidApplication::CesidAuthorizations::GetCesidApplication:0x00007fa3d200c1c0> Did you mean? allow_mass_assignment_of
Rspec file
require 'rails_helper'
describe Cesid::Application, :type => :controller do
describe 'cesid application' do
before do
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
end
it 'returns true if the id_token is present' do
expect(Cesid::Application.send('id_token_available?')).to eql(true)
end
end
end
Rspec version
3.5.4
This is honestly starting to drive me crazy
I see three issues:
You call allow_any_instance_of in a context in which it is not defined. allow_any_instance_of can be used in before blocks. I need to see your RSpec code to be more specific.
Actually your code is called on the ApplicationController, not on the module, therefore you need to change your stub to
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
Currently id_token_param will not be called at all, because id_token_available? checks the instance variable and not the return value of the id_token method that calls the id_token_param. Just change the id_token_available? to:
def id_token_available?
id_token.present?
end
There's a much better way of going about this test. The type: :controller metadata on your spec gives you an anonymous controller instance to work with.
Here's an example of how you could write this to actually test that the before_action from your module is used:
describe Cesid::Application, type: :controller do
controller(ApplicationController) do
def new
render plain: 'Hello'
end
end
describe 'cesid before_action' do
before(:each) do
routes.draw { get 'new' => 'anonymous#new' }
cookies[:id_token] = id_token
allow(Auth).to receive(:new).with(id_token)
.and_return(instance_double(Auth, valid?: false))
get :new
end
context 'when id token is available' do
let(:id_token) { 'hello' }
it 'sets #cesid_auth' do
expect(assigns(:cesid_auth)).to be_present
end
end
context 'when id token is unavailable' do
let(:id_token) { '' }
it 'does not set #cesid_auth' do
expect(assigns(:cesid_auth)).to be_nil
end
end
end
end
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
If I was using RSpec I could test if a method is being called like so:
expect(obj).to receive(:method)
What is the equivalent in MiniTest? I have a model, Post, which has a before_validation callback which runs a method create_slug. In my test test/models/post_test.rb I want to ensure that the create_slug method is being called when calling post.save.
The Minitest::Spec documentation says that I can use a method must_send to check if a method is called. However, when I try #post.must_send :create_slug I receive the following error:
NoMethodError: undefined method `must_send' for #<Post:0x007fe73c39c648>
I am including Minitest::Spec in my test_helper.rb file:
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'minitest/spec'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end
An excerpt of my test:
describe Post do
before do
#post = FactoryGirl.build(:post)
end
describe "when saving" do
it "calls the create_slug method before validation" do
#post.must_send :create_slug
#post.save
end
end
end
What you are asking for here is a partial mock. This means you have a real object and you want to mock out one method and verify it was called. Minitest::Mock does not support partial mocks out of the box, but I'll try to show you how you can accomplish this anyway. Do a search or two on "partial mock" and see what folks have to say about it.
The easiest way to support partial mocks is to use a different mocking library like Mocha. Just add gem "mocha" to your Gemfile and you should be good to go.
describe Post do
before do
#post = FactoryGirl.build(:post)
end
describe "when saving" do
it "calls the create_slug method before validation" do
#post.expects(:create_slug).returns(true)
#post.save
end
end
end
But if you really want to use Minitest::Mock there is a way to make it work. It requires a new mock object and using ruby to redefine the create_slug method. Oh, and global variables.
describe Post do
before do
#post = FactoryGirl.build(:post)
end
describe "when saving" do
it "calls the create_slug method before validation" do
$create_slug_mock = Minitest::Mock.new
$create_slug_mock.expect :create_slug, true
def #post.create_slug
$create_slug_mock.create_slug
end
#post.save
$create_slug_mock.verify
end
end
end
Wow. That's ugly. Looks like an oversight, right? Why would Minitest make partial mocks so difficult and ugly? This is actually a symptom of a different problem. The question isn't "How do I use partial mocks?", the question is "How do I test the expected behavior of my code?" These tests are checking the implementation. What if you renamed the create_slug method? Or what if you changed the mechanism that created a slug from a callback to something else? That would require that this test change as well.
Instead, what if instead your tests only checked the expected behavior? Then you could refactor your code and change your implementation all without breaking the test. What would that look like?
describe Post do
before do
#post = FactoryGirl.build(:post)
end
describe "when saving" do
it "creates a slug" do
#post.slug.must_be :nil?
#post.save
#post.slug.wont_be :nil?
end
end
end
Now we are free to change the implementation without changing the tests. The tests cover the expected behavior, so we can refactor and clean the code without breaking said behavior. Folks ask why Minitest doesn't support partial mocks. This is why. You very rarely need them.
For this purpose Minitest has a .expect :call which allows you to check if method is getting called:
describe Post do
before do
#post = FactoryGirl.build(:post)
end
describe "when saving" do
it "calls the create_slug method before validation" do
mock_method = MiniTest::Mock.new
mock_method.expect :call, "return_value", []
#post.stub :create_slug, mock_method do
#post.save
end
mock_method.verify
end
end
end
If #post.create_slug was called, test will pass. Otherwise test will raise a MockExpectationError.
Unfortunately this feature is not documented very well. I found this answer from here: https://github.com/seattlerb/minitest/issues/216
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
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.