I've been trying to do this for a couple of days now, but I can't figure it out. I have the following code in my controller:
#some_object = #current_user.some_method
In my spec, I want to attach a should_receive hook on that method, but I can't make it work. I've tried all of these, but none of them work:
assigns[:current_user].should_receive(:some_method).at_least(:once) # expected 1, got 0
User.should_receive(:some_method).at_least(:once) # expected 1, got 0
How is the correct way of testing this? I'm running this in my spec, and login is working:
setup :activate_authlogic
...
UserSession.create(users(:rune))
Thanks!
One example comes from the Ruby on Rails Tutorial. Rather than setting and reading #current_user directly, it defines two helper methods:
def current_user=(user)
#current_user = user
end
def current_user
#current_user
end
Later, they access this method in the tests using the controller method:
def test_sign_in(user)
controller.current_user = user
end
Using this methodology, you should be able to use
controller.current_user.should_receive(:some_method).at_least(:once)
You can’t call something like in the controllers:
expect(current_user).to be_present
expect(user_signed_in?).to be_true
So to do so, you can do this :
module ControllerMacros
def current_user
user_session_info = response.request.env['rack.session']['warden.user.user.key']
if user_session_info
user_id = user_session_info[0][0]
User.find(user_id)
else
nil
end
end
def user_signed_in?
!!current_user
end
end
You can either include the ControllerMacros in the top of the controller spec or include it in the spec_helper.rb like so :
RSpec.configure do |config|
config.include ControllerMacros, type: :controller
end
Related
I am using the HTTParty gem to make a call to the GitHub API to access a list of user's repos.
It is a very simple application using Sinatra that displays a user's favourite programming language based on the most common language that appears in their repos.
I am a bit stuck on how I can write an RSpec expectation that mocks out the actual API call and instead just checks that json data is being returned.
I have a mock .json file but not sure how to use it in my test.
Any ideas?
github_api.rb
require 'httparty'
class GithubApi
attr_reader :username, :data, :languages
def initialize(username)
#username = username
#response = HTTParty.get("https://api.github.com/users/#{#username}/repos")
#data = JSON.parse(#response.body)
end
end
github_api_spec.rb
require './app/models/github_api'
require 'spec_helper'
describe GithubApi do
let(:github_api) { GithubApi.new('mock_user') }
it "receives a json response" do
end
end
Rest of the files for clarity:
results.rb
require 'httparty'
require_relative 'github_api'
class Results
def initialize(github_api = Github.new(username))
#github_api = github_api
#languages = []
end
def get_languages
#github_api.data.each do |repo|
#languages << repo["language"]
end
end
def favourite_language
get_languages
#languages.group_by(&:itself).values.max_by(&:size).first
end
end
application_controller.rb
require './config/environment'
require 'sinatra/base'
require './app/models/github_api'
class ApplicationController < Sinatra::Base
configure do
enable :sessions
set :session_secret, "#3x!ilt£"
set :views, 'app/views'
end
get "/" do
erb :index
end
post "/user" do
#github = GithubApi.new(params[:username])
#results = Results.new(#github)
#language = #results.favourite_language
session[:language] = #language
session[:username] = params[:username]
redirect '/results'
end
get "/results" do
#language = session[:language]
#username = session[:username]
erb :results
end
run! if app_file == $0
end
There are multiple ways you could approach this problem.
You could, as #anil suggested, use a library like webmock to mock the underlying HTTP call. You could also do something similar with VCR (https://github.com/vcr/vcr) which records the results of an actual call to the HTTP endpoint and plays back that response on subsequent requests.
But, given your question, I don't see why you couldn't just use an Rspec double. I'll show you how below. But, first, it would be a bit easier to test the code if it were not all in the constructor.
github_api.rb
require 'httparty'
class GithubApi
attr_reader :username
def initialize(username)
#username = username
end
def favorite_language
# method to calculate which language is used most by username
end
def languages
# method to grab languages from repos
end
def repos
repos ||= do
response = HTTParty.get("https://api.github.com/users/#{username}/repos")
JSON.parse(response.body)
end
end
end
Note that you do not need to reference the #username variable in the url because you have an attr_reader.
github_api_spec.rb
require './app/models/github_api'
require 'spec_helper'
describe GithubApi do
subject(:api) { described_class.new(username) }
let(:username) { 'username' }
describe '#repos' do
let(:github_url) { "https://api.github.com/users/#{username}/repos" }
let(:github_response) { instance_double(HTTParty::Response, body: github_response_body) }
let(:github_response_body) { 'response_body' }
before do
allow(HTTParty).to receive(:get).and_return(github_response)
allow(JSON).to receive(:parse)
api.repos
end
it 'fetches the repos from Github api' do
expect(HTTParty).to have_received(:get).with(github_url)
end
it 'parses the Github response' do
expect(JSON).to have_received(:parse).with(github_response_body)
end
end
end
Note that there is no need to actually load or parse any real JSON. What we're testing here is that we made the correct HTTP call and that we called JSON.parse on the response. Once you start testing the languages method you'd need to actually load and parse your test file, like this:
let(:parsed_response) { JSON.parse(File.read('path/to/test/file.json')) }
You can mock those API calls using https://github.com/bblimke/webmock and send back mock.json using webmock. This post, https://robots.thoughtbot.com/how-to-stub-external-services-in-tests walks you through the setup of webmock with RSpec (the tests in the post mock GitHub API call too)
To be more particular, I'm talking about sentry-raven and sinatra here. I saw examples testing sinatra applications, or middlewares. But I didn't see ones testing if some particular middleware is present. Or should I be testing behavior, not configuration (or how should I call it)?
The important thing (I'd say) is the behaviour, but if you wish to check for middleware there are 2 ways I'd suggest after taking a delve into the Sinatra source (there are possibly much easier/better ways):
The env
In the Sinatra source there's a method that uses the env to check if a middleware is already present:
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? #app.call(env) : super
end
You could do the same thing in a route, e.g.
get "/env-keys" do
env.keys.inspect
end
It'll only show you the middleware if it's inserted something in env hash, e.g.
class MyBad
def initialize app, options={}
#app = app
#options = options
end
def call env
#app.call env.merge("mybad" => "I'm sorry!")
end
end
output:
["SERVER_SOFTWARE", "SERVER_NAME", "rack.input", "rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "REQUEST_METHOD", "REQUEST_PATH", "PATH_INFO", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT", "HTTP_USER_AGENT", "HTTP_DNT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "GATEWAY_INTERFACE", "SERVER_PORT", "QUERY_STRING", "SERVER_PROTOCOL", "rack.url_scheme", "SCRIPT_NAME", "REMOTE_ADDR", "async.callback", "async.close", "rack.logger", "mybad", "rack.request.query_string", "rack.request.query_hash", "sinatra.route"]
It's near the end of that list.
The middleware method
There's also a method called middleware in Sinatra::Base:
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + #middleware
else
#middleware
end
end
Call it in the class definition of a modular app and you can get the middlewares in an array:
require 'sinatra/base'
class AnExample < Sinatra::Base
use MyBad
warn "self.middleware = #{self.middleware}"
output:
self.middleware = [[MyBad, [], nil]]
There may be a way to get it from Sinatra::Application, but I haven't looked.
With help from ruby-raven guys, we've got this:
ENV['RACK_ENV'] = 'test'
# the app: start
require 'sinatra'
require 'sentry-raven'
Raven.configure(true) do |config|
config.dsn = '...'
end
use Raven::Rack
get '/' do
'Hello, world!'
end
# the app: end
require 'rspec'
require 'rack/test'
Raven.configure do |config|
logger = ::Logger.new(STDOUT)
logger.level = ::Logger::WARN
config.logger = logger
end
describe 'app' do
include Rack::Test::Methods
def app
#app || Sinatra::Application
end
class TestRavenError < StandardError; end
it 'sends errors to sentry' do
#app = Class.new Sinatra::Application do
get '/' do
raise TestRavenError
end
end
allow(Raven.client).to receive(:send).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven.client).to have_received(:send)
end
end
Or if raven sending requests is in the way (when tests fail because of raven sending requests, and not because of the underlying error), one can disable them:
Raven.configure(true) do |config|
config.should_send = Proc.new { false }
end
And mock Raven.send_or_skip instead:
...
allow(Raven).to receive(:send_or_skip).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven).to have_received(:send_or_skip)
...
I'm trying to test a helper in a Padrino (Sinatra) app. My helper method is itself calling Padrino core helper methods but they are undefined. The error appears only in RSpec, while the app works fine. So the way I'm including my helper in RSpec makes it loose "Padrino scope" but I don't know how to bring Padrino helper's scope properly in my RSpec environment.
My helper:
module AdminHelper
Sort = Struct.new(:column, :order)
def sort_link(model, column)
order = sorted_by_this?(column) ? 'desc' : 'asc'
link_to mat(model, column), url(:pages, :index, sort: column, order: order)
end
def sorted_by_this?(column)
column.to_s == #sort.column && #sort.order == 'asc'
end
end
Lenstroy::Admin.helpers AdminHelper
My spec:
describe AdminHelper do
before(:all) do
class AdminHelperClass
include AdminHelper
end
end
subject(:helper) { AdminHelperClass.new }
describe '#sort_link' do
context "with :pages and :title parameters" do
before do
sort = AdminHelperClass::Sort.new('title', 'asc')
helper.instance_variable_set('#sort', sort)
end
subject { helper.sort_link(:pages, :title) }
it { should match(/<a href=([^ ]+)pages/) }
end
end
end
Results in error:
1) AdminHelper#sort_link with :pages and :title parameters
Failure/Error: subject { helper.sort_link(:pages, :title) }
NoMethodError:
undefined method `mat' for #<AdminHelperClass:0x007f1d951dc4a0>
Including a helper where mat is defined doesn't work, as one method is dependent on another helper and it goes on and on...
Update
In my spec helper I have:
def app(app = nil, &blk)
#app ||= block_given? ? app.instance_eval(&blk) : app
#app ||= Lenstroy::Admin
#app.register Padrino::Helpers
#app.register Padrino::Rendering
#app
end
in my spec I have:
it "returns link to resource with sort parameters" do
app do
get '/' do
sort_link(:pages, :title)
end
end
get "/"
last_response.body.should =~ /<a href=([^ >]+)pages/
end
And now tests fail, last_response.body is ''.
Method #mat is defined in Padrino::Admin::Helpers::ViewHelpers. You can do
class AdminHelperClass
include Padrino::Admin::Helpers::ViewHelpers
include AdminHelper
end
Update:
If your methods are really dependent on all these routes and helpers you should consider doing full mockup of your app like this:
def mock_app(base=Padrino::Application, &block)
#app = Sinatra.new(base, &block)
#app.register Padrino::Helpers
#app.register Padrino::Rendering
# register other things
end
def app
Rack::Lint.new(#app)
end
mock_app do
get '/' do
sort_link(my_model, my_column)
end
end
get "/"
assert_equal "some test text", body
Here's how it's done in padrino-admin: https://github.com/padrino/padrino-framework/blob/master/padrino-admin/test/test_admin_application.rb
I was having the same problem (and getting very frustrated tracking down the modules and including them). So far, I've got my specs working by:
1) Explicitly defining my module (as explained in how to use padrino helper methods in rspec)
module MyHelper
...
end
MyApp::App.helpers MyHelper
2) Automatically including helpers at the top of my spec. (Right now I only have one helper spec, but in the future I might try to move this into spec_helper.rb.)
describe MyHelper do
let(:helpers) { Class.new }
before { MyApp::App.included_modules.each { |m| helpers.extend m } }
subject { helpers }
it 'blah' do
expect(subject.helper_method).to eq 'foo'
end
end
I've been trying Padrino framework in one of my project, and there is one thing that really annoys me. I want to implement just for instance a user registration process using OmniAuth and want to break my request handler (controller's action) to separate methods, like this:
get ":provider/callback" do
#user = find_the_user_by_oauth(request)
create_user unless #user
store_user_in_session
end
def find_the_user_by_oauth(request)
#...
end
def store_user_in_session
session[:user_id] = #user.id
end
I know it would be nicer to push the logic to the model layer, but my question is, how could I break a controller logic to separated methods and share information among them (like using instance variables). In Rails I created these methods in the private scope of my controller, but here I should extend the Application class because it throws Undefined method exception for the previous code. I tried Helpers, but helpers don't know the instance variables, so you should pass the variables every time.
What is the good way to make my controller actions clean in Padrino?
To define a method inside an Padrino Controller you can use define_method instead of def.
For your example, do something like this:
Admin.controllers :dummy do
define_method :find_the_user_by_oauth do |request|
request.params["username"]
# ...
end
define_method :store_user_in_session do
session[:user_id] = #user
end
get :test do
#user = find_the_user_by_oauth(request)
create_user unless #user
store_user_in_session()
session.inspect
end
end
Padrino runs the block sent to Admin.controllers using instance_eval.
See this answer for the differences https://stackoverflow.com/a/3171649 between define_method and def
possible offtopic, but would you consider to use Espresso Framework instead.
then you'll can solve your issue as simple as:
class App < E
def index provider, action = 'callback'
#user = find_the_user_by_oauth
create_user unless #user
store_user_in_session
end
private
def find_the_user_by_oauth
# provider, action are accessed via `action_params`
# action_params[:provider]
# action_params[:action]
end
def store_user_in_session
session[:user_id] = #user.id
end
end
I want to override authenticate_user! and current_user method of devise gem in my application Controller can you please help me with regards to that
Thanks
You may be able to monkey-patch it like:
module Devise
module Controllers
module Helpers
def authenticate_user!
#do some stuff
end
end
end
end
But I would ask what the ultimate goal is, because Devise has some customizability built into it already, and overriding these methods makes me wonder "why use Devise at all?"
On overriding how a user is authenticated:
Devise uses Warden under the hood
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb
So you can just add a new strategy in Warden to authenticate your users. See
https://github.com/hassox/warden/wiki/Strategies
You should not need to override current_user. What challenge are you facing ?
Do you need a different model returned ?
If you want to add code to authenticate_user!
class DuckController < ApplicationController
before_action :authenticate_duck
...
private
def authenticate_duck
#use Devise's method
authenticate_user!
#add your own stuff
unless current_user.duck.approved?
flash[:alert] = "Your duck is still pending. Please contact support for support."
redirect_to :back
end
end
end
You have to create a custom class to override the default Devise behavior:
class CustomFailure < Devise::FailureApp
def redirect_url
#return super unless [:worker, :employer, :user].include?(scope) #make it specific to a scope
new_user_session_url(:subdomain => 'secure')
end
# You need to override respond to eliminate recall
def respond
if http_auth?
http_auth
else
redirect
end
end
end
And in your config/initializers/devise.rb:
config.warden do |manager|
manager.failure_app = CustomFailure
end
But I suggest check out the Devise documentation :)
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
At application_controller.rb you can overwrite just as you want:
def authenticate_user!
super # just if want the default behavior
call_a_method_to_something if current_user
# or
call_a_method_to_something if current_user.nil?
end