RSpec - uninitialized constant for instance double - api

I am getting a name error for a small API i am building with RSpec, using TDD methodology
NameError:
uninitialized constant ExpenseTracker::API::Ledger
API.rb
require "sinatra/base"
require "json"
module ExpenseTracker
class API < Sinatra::Base
def initialize(ledger: Ledger.new) # default value so callers can just say API.new and get the default
#ledger = ledger
super() # rest of initialization from Sinatra
end
# Later, callers do this ledger: Ledger.new
app = API.new()
post '/expenses' do
JSON.generate('expense_id' => 42)
end
get '/expenses/:date' do
JSON.generate([])
end
end
end
api_spec.rb
require_relative "../../../app/api"
require "rack/test"
module ExpenseTracker
RecordResult = Struct.new(:success?, :expense_id, :error_message)
RSpec.describe API do
include Rack::Test::Methods
def app
API.new(ledger: ledger)
end
let(:ledger) { instance_double('ExpenseTracker::Ledger') }
describe "POST /expense" do
context "when the expenseis successfully recorded" do
it 'returns the expense id'do
allow(ledger).to receive(:record)
.with(expense)
.and_return(RecordResult.new(true, 417, nil))
post '/expense', JSON.generate(expense)
parsed = JSON.parse(last_response.body)
expect(parsed).to include('expense_id': 417)
end
it 'responds with a 200 (OK)'
end
context "when the expense fails validation" do
it 'returns an error message'
it 'responds with 422 (Unprocessable entity)'
end
end
end
end
Below is the error that i am getting :
An error occurred while loading ./api_spec.rb.
Failure/Error:
def initialize(ledger: Ledger.new) # default value so callers can just say API.new and get the default
#ledger = ledger
super() # rest of initialization from Sinatra
end
NameError:
uninitialized constant ExpenseTracker::API::Ledger
/Users/Denis/Desktop/TDD/Effective_Testing_with_Rspec3/04-acceptance-specs/expense_tracker/app/api.rb:7:in `initialize'
/Users/Denis/Desktop/TDD/Effective_Testing_with_Rspec3/04-acceptance-specs/expense_tracker/app/api.rb:13:in `<class:API>'
/Users/Denis/Desktop/TDD/Effective_Testing_with_Rspec3/04-acceptance-specs/expense_tracker/app/api.rb:5:in `<module:ExpenseTracker>'
/Users/Denis/Desktop/TDD/Effective_Testing_with_Rspec3/04-acceptance-specs/expense_tracker/app/api.rb:4:in `<top (required)>'
./api_spec.rb:1:in `require_relative'
./api_spec.rb:1:in `<top (required)>'
Am I missing something? i have tried to play around with passing the ledger: Ledger.new to app = API.new but not getting past this error.
any help or ideas would be great !

Ok figured this one out, after a lot of head banging on the desk.
I had to comment out the app = API.new as this is not yet implemented, every time i would load the file in the spec it would error me out.
# Later, callers do this ledger: Ledger.new
app = API.new()
now on to the next failing spec lol !

Related

Rspec testing API with rack protection

I am trying to test my API with an rspec integration(request) test.
I go to my api endpoint at 0.0.0.0:3000/api/regions in a browser and it returns my data, I get a session id and looks like everything is working.
I am using rack protection in my API:
module Api
class Root < Grape::API
use Rack::Protection, instrumenter: ActiveSupport::Notifications
use Rack::Protection::AuthenticityToken
prefix 'api'
helpers do
def warden
request.env['warden']
end
end
version 'v1', using: :header, vendor: 'vendor', strict: false do
mount Resources::V1::Search => '/'
mount Resources::V1::Regions => '/'
end
end
end
The api resource:
module Resources
module V1
class Regions < Grape::API
resource :regions do
get do
# Some code...
end
end
end
end
end
spec_helper.rb
[...]
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include Rack::Test::Methods, type: :request
config.include Module.new { def app; Api::Root; end }, type: :request
config.include Warden::Test::Helpers
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
end
Here is a test:
describe Resources::V1::Regions do
describe 'GET /regions' do
it 'returns some data' do
get '/api/regions'
[... expectations - doesn't get here...]
end
end
end
Here is the error:
RuntimeError:
you need to set up a session middleware *before* Rack::Protection::RemoteToken
# ./spec/requests/api/region_spec.rb:6:in `block (3 levels) in <top (required)>'
Which comes from here: https://github.com/rkh/rack-protection/blob/master/lib/rack/protection/base.rb#L85
So I need to add rack session middleware, right?
I add use Rack::Session::Cookie, secret: '...' to my api which gets me to request.env['warden'] being nil (another question another time).
Now, however, when I load the endpoint with the browser I get:
undefined method `each' for #<ActionDispatch::Request::Session:0x7f7bf9e521e0 not yet loaded>
which raises over here: https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L158
I suspect I don't need to use Rack::Session::Cookie, as something else is doing it when loaded by the server, but I need to add something for the tests to work. No idea what that something is.
Please let me know if you need any other info.
Versions:
grape (0.6.1)
rails (4.0.2)
rack-protection (1.5.2)
rspec (2.14.1)
I was solving this by adding a 'rack.session' hash to the 3rd argument of my request i.e get '/api/regions', {}, {'rack.session' => {}}
But I found a better way: https://github.com/heavysixer/grape-on-rack/pull/1
which adds sessions and solves the warden issue at the same time.
RSpec.configure do |config|
[...]
rack_app = Module.new do
def app
#app ||= Rack::Builder.new do
use Rack::Session::Cookie
Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }
use Warden::Manager do |manager|
manager.default_strategies :password, :basic
# manager.failure_app = Acme::BadAuthentication
end
run Api::Root
end
end
end
config.include rack_app, type: :request
end
Marking as answer unless anyone has anything better.

Rspec to mock my call to external api from my function and return custom response back to the function

So i have written a module which has a function. And now i am trying to test the function.
My question here is that how do i use mocking or stub etc. to test that for a random userid we grab the request to github from the function and return a custom json back to the function.?
Module being tested:
require 'json'
require 'httparty'
module util
GITHUB_URL = 'http://github.com/'
def get_name(id)
begin
response = HTTParty.get("#{GITHUB_URL}#{id}.json")
data = JSON.parse(response.body)
return data.first['actor_attributes']['name']
rescue Exception => e
return nil
end
end
end
My Rspec file:
# coding: UTF-8
require 'spec_helper'
class DummyClass
end
describe 'GET_NAME' do
before(:each) do
#dummy_class = DummyClass.new
#dummy_class.extend(util)
end
context 'for invalid Github ID' do
it 'return nil' do
expect(#dummy_class.getName('invalid')).to be_nil
end
end
end
Thank you all for your help.
Checkout https://github.com/bblimke/webmock or http://fakeweb.rubyforge.org/
Then you do something like:
stub_request(
:post, "#{GITHUB_URL}1.json"
).to_return(
body: { actor_attributes: {name: "Bill"} }.to_json
)
I'm a bigger fan of Webmock than Fakeweb (it's got a few more features), but they'll both do for you, and it's pretty easy to switch between them.

Defining my Custom devise authentication strategy throws 'load_missing_constant' error

In my app, I am trying to define a custom strategy but it throws some errors... Here's what I am doing:
# myrailsapp/lib/devise/strategies
require 'devise/strategies/database_authenticatable'
module Devise
module Strategies
class CustomAuth < Devise::Strategies::DatabaseAuthenticatable
def valid?
Rails.logger.info("\n\nIn CustomAuth.valid\n\n")
true
end
def authenticate!
Rails.logger.info("\n\nCalled My Authenticate!\n\n")
super
end
end
end
end
Warden::Strategies.add(:custom_auth, Devise::Strategies::CustomAuth)
# config/initializer.rb
config.warden do |manager|
# manager.strategies.add(:custom_auth, Devise::Strategies::CustomAuth)
manager.default_strategies(:scope => :user).unshift :custom_auth
end
When I try to run this, I get this error:
/usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:490:in load_missing_constant': Expected app/helpers/custom_auth.rb to define CustomAuth (LoadError)
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:181:inconst_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:in each'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:inconst_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:501:in load_missing_constant'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:181:inconst_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:in each'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:inconst_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:501:in load_missing_constant'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:181:inconst_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:in each'
from /usr/lib/ruby/gems/1.8/gems/activesupport-3.1.1/lib/active_support/dependencies.rb:179:inconst_missing'
from /home/rjain/src/qbol/tapp/config/initializers/devise.rb:212
Now, when I move the same file to my app/helpers directory, I get the same error, but if I remove the module Devise and module Strategies, things start to work. Two questions:
How to ensure that I can create the file in my lib/devise/strategies folder?
How to make it work so that I can use it within Devise::Strategies module as I described above?

Mongoid dynamic finder with Mongoid::Errors::DocumentNotFound exception raised

I'm building a REST api for this project that uses Mongoid.
I've setup the following to catch the Mongoid::Errors::DocumentNotFound exception:
rescue_from Mongoid::Errors::DocumentNotFound in my base controller
In my controller I've this query code:
#current_account.users.find(:first, :conditions => {:name => "some_name"})
The above query just returns nil. It doesn't raise the exception.
Tried with another syntax as well:
User.find(:conditions => {:name => "same"}).first
All those methods just runs where internally and afaik where doesn't raise exception, its simply returns []
So what can be the solution to this? I want partially dynamic finder but should raise the exception too?
I've met same problem today, and found another solution.
Set raise_not_found_error to false. so your config/mongoid.yml should be
development:
host: localhost
port: 10045
username: ...
password: ...
database: ...
raise_not_found_error: false
from http://mongoid.org/docs/installation/configuration.html
I believe that Mongoid will only raise a DocumentNotFound exception when using the find method by passing in an object's id (and not with conditions). Otherwise it will return nil. From the Mongoid source:
# lib/mongoid/errors/document_not_found.rb
# Raised when querying the database for a document by a specific id which
# does not exist. If multiple ids were passed then it will display all of
# those.
You will have to check manually to see if you got any results and either raise the DocumentNotFound exception yourself (not great), or raise your own custom exception (better solution).
An example of the former would be something like this:
raise Mongoid::Errors::DocumentNotFound.new(User, params[:name]) unless #current_account.users.first(:conditions => {:name => params[:name]})
Update: I haven't tested any of this, but it should allow you to make calls like (or at least point you in the right direction - i hope!):
#current_account.users.where!(:conditions => {:name => params[:name]})
Which will throw a custom Mongoid::CollectionEmpty error, if the collection returned from the query is empty. Note that it's not the most efficient solution, since in order to find out if the returned collection is empty - it has to actually process the query.
Then all you need to do is rescue from Mongoid::CollectionEmpty instead (or as well).
# lib/mongoid_criterion_with_errors.rb
module Mongoid
module Criterion
module WithErrors
extend ActiveSupport::Concern
module ClassMethods
def where!(*args)
criteria = self.where(args)
raise Mongoid::EmptyCollection(criteria) if criteria.empty?
criteria
end
end
end
end
class EmptyCollection < StandardError
def initialize(criteria)
#class_name = criteria.class
#selector = criteria.selector
end
def to_s
"Empty collection found for #{#class_name}, using selector: #{#selector}"
end
end
end
# config/application.rb
module ApplicationName
class Application < Rails::Application
require 'mongoid_criterion_with_errors'
#...snip...
end
end
# app/models/user.rb
class User
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Criterion::WithErrors
#...snip...
end

Hello World rack middleware with rails 3: how to process body of all requests

i want to try out a simple rack middleware "hello world", but i seem to get stuck.
it looks like the main sytax changed, since some examples use this code:
require 'rack/utils'
class FooBar
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
body.body << "\nHi from #{self.class}"
[status, headers, body]
end
end
produces an error:
undefined method `<<' for #<ActionDispatch::Response:0x103f07c48>
even when i look at other codes out there, i cannot seem to get them running with rails 3.0.3.
here are my concrete questions:
how can i get a simple rack middleware to run and modify the body of any output from a rails app?
where should i put the Rails.application.config.middleware.use declaration? (i created an own initializer in config/initializers for that)
thanks a lot in advance!
This should do what you want it to:
# in config/application.rb
config.middleware.use 'FooBar'
# in config/initializers/foo_bar.rb
class FooBar
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
[status, headers, response.body << "\nHi from #{self.class}"]
end
end
Be advised, that on just about every other request (at least on Rails 3.0.3), this will fail due to another middleware (Rack::Head) because it sends an empty request when content is unchanged. We are in this example depending on being able to call response.body, but in fact, the last member of the array can be anything that responds to .each.
Ryan Bates goes over Rack pretty well here:
http://asciicasts.com/episodes/151-rack-middleware
http://railscasts.com/episodes/151-rack-middleware
And the official Rails guide is pretty good too:
http://guides.rubyonrails.org/rails_on_rack.html
And of course the official Rack spec:
http://rack.rubyforge.org/doc/SPEC.html
Rails 3.2.12+:
previous answer does not work for Rails 3.2.12+
This one does:
# in config/application.rb
config.middleware.use 'FooBar'
# in config/initializers/foo_bar.rb
class FooBar
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
response.body += "\nHi from #{self.class}"
# response.body << "..." WILL NOT WORK
[status, headers, response]
end
end