How to stub a method from a module in a controller? - ruby-on-rails

lib/modules/api.rb:
module Api
require 'net/http'
require 'json'
def send_get_request(url, body)
# some logic
end
end
The controller:
class DashboardController < ApplicationController
include Api
def index
response = send_get_request(_some_, _params_)[:json]
#something = response['something']
end
end
How do I stub the send_get_request method? I tried a feature test:
require 'rails_helper'
describe 'visiting users page'
it 'shows users page' do
visit '/'
allow_any_instance_of(Api).to receive(:send_get_request).with(any_args).and_return({json: {'total_paying' => '3'}})
within('#side-menu') do
click_link 'Users'
end
expect(page).to have_selector('.page-header', text: 'Users')
end
end
but apparently it doesn't work (the test fails because real send_get_request method gets called with params that are incorrect (which they are in tests).
When I tried
expect_any_instance_of(Api).to receive(:send_get_request).once
That expectation passed.

Well, it's the controller that will receive the message. The test, as a client, does not care about how the method is defined, "normally" or mixed-in.
If it were a controller spec, you'd be able to do this:
allow(controller).to receive(:send_get_request).with(...)
As for the feature specs, don't stub anything in there. It's supposed to be the "real" interaction. In feature specs you use capybara (or something) to fill the forms and then check the database (to see if a user got created, etc.)
To shield your feature specs from external api, you can use VCR gem. It basically runs your external query one time, writes the response into a file and then, on subsequent runs, just plays it back, without contacting external api again.

Related

Rspec Testing: Coverage for controllers which accept api requests from another server not working

Attempting to write Rspec tests for a controller which accepts api requests from another server/service.
The code below accepts a request launched from another service for an authenticated user in the form /servers/users/some/create (post request)
module Servers
module Users
class SomeController < ::Servers::Users::BaseController
def create
some_service.create!
render json: {status: 'ok'}
end
end
def some_service
if current_user.randommodel.nil?
#some_service ||= ::SomeService.new(current_user)
end
randommodel is embedded within the user class and by default does not exist. Some_Service is responsible for creating this randommodel
class SetupMfaService
attr_accessor :current_user
def initialize(current_user)
#current_user = current_user
end
def create!
current_user.randommodel = randommodel.new
current_user.randommodel.save!
end
This service class has been fully covered with tests and works as expected
SomeService has been fully covered with tests and works completely fine. (It works). However when I attempt to test this method through doing:
require 'rails_helper'
require 'requests/servers/servers_api_context'
RSpec.describe '::Servers::Users::SomeController' do
include_context "Servers API"
let!(:user) { Fabricate(:user, { clear_password: password }) }
let(:request_auth_data) do
{
user_id: user.id,
account_id: user.account_id,
role: ''
}
end
let(:source_server) { 'web_application' }
let(:endpoint) { 'localhost' }
context 'create' do
subject do
servers_api_post '/servers/users/some/create'
end
it 'returns a 200' do
response = subject
expect(response.status).to be(200)
end
it 'returns the expected JSON data' do
response = subject
expect(response.data).to eql({})
end
end
end
Tests are failing with no method error 'somemodel' for nil:NilClass which means to me it is not able to detect the presence of current_user (Which is available in the base controller which my controller inherits from as attr_reader :current_user. There also exists a decoded_payload object which allows us to read the parameters present in the requests handled which contains the user id which I've tried to do a look up for in the controller below despite the fact this process is done in the BaseController that this controller inherits from too. Which leads me to the conclusion the way in which I am writing the test is wrong. How can I explicit the current user parameter in the rspec test for this controller file? ~
Logging in for requests is handled in BaseController where it is inherited down into my SomeController
def authenticate
#jwt = case request_source
when 'web_application'
::Servers::WebApplication::JwtAuthService.new(endpoint).decrypt_and_verify(jwe)
else
raise 'Unknown request source'
end
#current_user = User.find(decoded_payload[:user_id])
#current_account = current_user&.account
raise ::Api::Error::AccountNotFound.new if current_account.blank?
end
Assuming that by somemodel you mean current_user.randommodel.nil?, then your tests are not logged in. The tests declare request_auth_data but don't appear to do anything with it.
I can't tell you how to login, just that your tests must do it.
Also your tests reveal a bug in your controller. If a login is required the API should have responded with 401 Unauthorized, not an exception. Often this is handled by inheriting from a controller base class which requires and handles authorization.

Rails engine testing controller using helper method of main app in rspec

I have one main rails app, and I have an engine within it, now the engine has a different test, I need to test one controller which is using main app MailerHelper module. How can I access in my spec.
My engine's user controller
module ControlPanel
class UserController < ApplicationController
include MailerHelper
def index
#users = User.all
end
end
end
I am using MailerHelper have many other methods which sent mails. As this is controller is in my engine control panel, following is specification in my engine
require 'rails_helper'
describe ControlPanel::UsersController, type: :controller do
describe '#index' do
it 'should display all users' do
get :index
expect(response).to render_template('index')
end
end
end
The test case is just simple, but as in my engines User controller I am using my main application helper, so it can't take it, I have seen many replies but nothing working, like including in rails_spec and ::MailerHelper etc.

Using Session, Cookies or Current_something in Rspec request tests

I am trying to follow the new Rails testing convention with this new project I am working on. Therefore I setup unit tests, request tests and feature tests. The issue is that in the request tests, Capybara and the the session information are not supported.
In addition to having the current_user method setup by Devise, my application has another similar method called current_client. For some of my controllers, I need to check whether the current_user is logged in. This works with Devise authenticate_user! called in a before_action. But for some controllers, I also need to check if a client was selected first (for example if you want to add transactions, they need to be tied with the current client being worked on).
So I added another before_action method that checks if a client was also selected. This works well in feature tests with Capybara where I can mimic user loggin in and the user selecting a client to process. However in the request tests, It doesn`t work.
I first test trying to access the endpoint without a user being logged in and the response is not successful (as it should) and it redirects to the sign in page. But then I need to run my tests with a user logged in AND a client selected. Devise helpers provide a sign_in(user) method. However I can't get my current_user method to work and I can't seem to be able to set it up properly. So those tests are failing and redirecting to another page asking the user to select a client.
I have already tried many suggestions I saw. Like trying to stub the current_client method, trying to provide session info to the GET call. I even tried to check the Devise source code to find out how they can simulate the current_user method in Rspec and couldn't really find where the magic happens.
Here is the code
The current_client method looks like this
def current_client
current_client ||= Client.find(session[:client_id]) if session[:client_id]
rescue ActiveRecord::RecordNotFound
current_client = nil
end
This is how it is set once a user selects a client to process
def set_current_client(client)
session[:client_id] = client.id
end
This is the code in my test file
I first create 2 users so that I can test that user 1 cannot access user 2 transactions down the line. (I'm using FactoryBot)
I then create 2 clients (one for each user)
RSpec.describe "Rooms", :type => :request do
let!(:user) {create(:user)}
let!(:user2) {create(:user2)}
let!(:client) {create(:client, user: user)}
let!(:client) {create(:client, user: user2)}
The following works
describe 'User Not Signed In' do
describe 'GET #index' do
subject { get transact_path}
it "returns an unsuccessful response" do
subject
expect(response).to_not be_successful
end
it "redirects to sign in page" do
subject
expect(response).to redirect_to (new_user_session_path)
end
end
The following doesn't. The sign_in(user) method works in the before block and the redirect is not made to the sign in page. However, the response is not successful because the current_client is not set and I have tried to set it in so many ways in the before block without success.
describe 'User Signed In' do
before do
sign_in(user)
end
describe 'GET #index' do
it "returns a successful response" do
get transact_path
expect(response).to be_successful
end
end
end
I understand that the best practices moved away from controller tests because what view rendered or what instance variable assigned doesn't have anything to do with controllers in theory. But in my case, I'm simply trying to test my endpoints and right now I can't because I cannot setup my current_client.
I found a way around it by using a suggested solution by DHH himself.
Instead of trying to stub the current_client variable or try to jury rig something in the gut of ActionDispatch::Cookies, you simply need to do a POST or GET call to whatever controller is responsible for setting my current_client variable.
So for me the solution was to put the following code in a before do block
before do
sign_in(user)
get select_client_path, params: {id: client.id}
end

How to stub ApplicationController method in request spec

I am needing to stub the response of a current_user method in an Rspec/capybara request spec. The method is defined in ApplicationController and is using helper_method. The method should simply return a user id. Within the test, I'd like this method to return the same user id each time.
Alternatively, I could fix my problem by setting session[:user_id] in the spec (which is what current_user returns)... but that doesn't seem to work either.
Are either of these possible?
Edit:
Here is what I've got (it is not working. It just runs the normal current_user method).
require 'spec_helper'
describe "Login" do
before(:each) do
ApplicationController.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
Also not working:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
skalee seems to have provided the correct answer in the comment.
If the method you're trying to stub is an instance method (most likely) and not a class method then you need use:
ApplicationController.any_instance.stub(:current_user)
Here are a couple of examples of the basic form.
controller.stub(:action_name).and_raise([some error])
controller.stub(:action_name).and_return([some value])
In your particular case, I believe the proper form would be:
controller.stub(:current_user).and_return([your user object/id])
Here's a full working example from a project I work on:
describe PortalsController do
it "if an ActionController::InvalidAuthenticityToken is raised the user should be redirected to login" do
controller.stub(:index).and_raise(ActionController::InvalidAuthenticityToken)
get :index
flash[:notice].should eql("Your session has expired.")
response.should redirect_to(portals_path)
end
end
To explain my full example, basically what this does is verify that, when an ActionController::InvalidAuthenticityToken error is raised anywhere in the app, that a flash message appears, and the user is redirected to the portals_controller#index action. You can use these forms to stub out and return specific values, test an instance of a given error being raised, etc. There are several .stub(:action_name).and_[do_something_interesting]() methods available to you.
Update (after you added your code): per my comment, change your code so it reads:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
This works for me and gives me a #current_user variable to use in tests.
I have a helper that looks like this:
def bypass_authentication
current_user = FactoryGirl.create(:user)
ApplicationController.send(:alias_method, :old_current_user, :current_user)
ApplicationController.send(:define_method, :current_user) do
current_user
end
#current_user = current_user
end
def restore_authentication
ApplicationController.send(:alias_method, :current_user, :old_current_user)
end
And then in my request specs, I call:
before(:each){bypass_authentication}
after(:each){restore_authentication}
For anyone else who happens to need to stub an application controller method that sets an ivar (and was stymied by endless wanking about why you shouldn't do that) here's a way that works, with the flavour of Rspec circa October 2013.
before(:each) do
campaign = Campaign.create!
ApplicationController.any_instance.stub(:load_campaign_singleton)
controller.instance_eval{#campaign = campaign}
#campaign = campaign
end
it stubs the method to do nothing, and sets the ivar on rspec's controller instance, and makes it available to the test as #campaign.
For Rspec 3+ the new api is:
For a controller test, nice and short:
allow(controller).to receive(:current_user).and_return(#user)
Or for all instances of ApplicationController:
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(#user)
None of the provided responses worked for me. As in #matt-fordam's original post, I have a request spec, not a controller spec. The test just renders the view without launching a controller.
I resolved this by stubbing the method on the view as described in this other SO post
view.stub(:current_user).and_return(etc)

How do i test a module that extends my controllers?

I wanted to create a module because i needed to execute some methods upon every controller action of my application. Thus, i created a module and used a before_filter to my application controller, so that it runs for every action.
This module is like a controller extension, since it handles redirects and has some methods as well. I need to have a logged in user to check that, but for some reason, the macro that i have for my other controllers is not recognized :
require 'spec_helper'
require 'devise/test_helpers'
describe GameEngine::GameLoop do
login_user
it "should check tavern quest" do
...
end
end
I get that login_user is undefined, though it works for all my controller, where i need it. Why isn't it recognized on the module ?

Resources