How to test a Ruby Roda app using RSpec to pass an argument to app.new with initialize - ruby

This question probably has a simple answer but I can't find any examples for using Roda with RSpec3, so it is difficult to troubleshoot.
I am using Marston and Dees "Effective Testing w/ RSpec3" book which uses Sinatra instead of Roda. I am having difficulty passing an object to API.new, and, from the book, this is what works with Sinatra but fails with a "wrong number of arguments" error when I substitute Roda.
Depending on whether I pass arguments with super or no arguments with super(), the error switches to indicate that the failure occurs either at the initialize method or in the call to Rack::Test::Methods post in the spec.
I see that in Rack::Test, in the Github repo README, I may have to use Rack::Builder.parse_file("config.ru") but that didn't help.
Here are the two errors that rspec shows when using super without brackets:
Failures:
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: post '/users', JSON.generate(user)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
And when using super():
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: super()
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./app/api.rb:8:in `initialize'
# ./spec/unit/app/api_spec.rb:10:in `new'
# ./spec/unit/app/api_spec.rb:10:in `app'
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
This is my api_spec.rb:
require_relative '../../../app/api'
require 'rack/test'
module MbrTrak
RecordResult = Struct.new(:success?, :expense_id, :error_message)
RSpec.describe API do
include Rack::Test::Methods
def app
API.new(directory: directory)
end
let(:directory) { instance_double('MbrTrak::Directory')}
describe 'POST /users' do
context 'when the user is successfully recorded' do
it 'returns the user id' do
user = { 'some' => 'user' }
allow(directory).to receive(:record)
.with(user)
.and_return(RecordResult.new(true, 417, nil))
post '/users', JSON.generate(user)
parsed = JSON.parse(last_response.body)
expect(parsed).to include('user_id' => 417)
end
end
end
end
end
And here is my api.rb file:
require 'roda'
require 'json'
module MbrTrak
class API < Roda
def initialize(directory: Directory.new)
#directory = directory
super()
end
plugin :render, escape: true
plugin :json
route do |r|
r.on "users" do
r.is Integer do |id|
r.get do
JSON.generate([])
end
end
r.post do
user = JSON.parse(request.body.read)
result = #directory.record(user)
JSON.generate('user_id' => result.user_id)
end
end
end
end
end
My config.ru is:
require "./app/api"
run MbrTrak::API

Well roda has defined initialize method that receives env as an argument which is being called by the app method of the class. Looks atm like this
def self.app
...
lambda{|env| new(env)._roda_handle_main_route}
...
end
And the constructor of the app looks like this
def initialize(env)
When you run your config.ru with run MbrTrack::API you are actually invoking the call method of the roda class which looks like this
def self.call(env)
app.call(env)
end
Because you have redefined the constructor to accept hash positional argument this no longer works and it throws the error you are receiving
ArgumentError:
wrong number of arguments (given 0, expected 1)
Now what problem are you trying to solve, if you want to make your API class configurable one way to go is to try out dry-configurable which is part of the great dry-ruby gem collection.
If you want to do something else feel free to ask.
It has been a long time since you posted your question so hope you will still find this helpful.

Related

Ruby basic RSpec test does not pass

I'm not able to understand why the following Rspec test does not pass -
require "rspec"
require_relative "file-to-be-tested"
describe Customer do
it "is valid with firstname" do
customer = Customer.new("handy")
expect(customer).to be_valid
end
end
for the corresponding Class definition -
class Customer
attr_reader :firstname
def initialize(firstname)
#firstname = firstname
end
end
these two code snippets are in separate files in the same folder, so when i run ~rspec <first-filename> in the terminal, I get the following error -
F
Failures:
1) Customer is valid with firstname
Failure/Error: expect(customer).to be_valid
expected #<Customer:0x007f90e50f3110> to respond to `valid?`
# ./poodr/poodr_rspec.rb:8:in `block (2 levels) in <top (required)>'
Finished in 0.00551 seconds (files took 0.52876 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./poodr/poodr_rspec.rb:6 # Customer is valid with firstname
be_valid is an rspec-rails method, but it looks like you're using just straight rspec. you could do something like:
require "rspec"
require_relative "file-to-be-tested"
describe Customer do
it "is valid with firstname" do
expect { Customer.new('handy') }.to_not raise_error
end
end
What are you expecting the to be_valid test to do? The issue is that the Customer class has no method called valid? which your test is trying to test.
A hack to move your test along if your doing test driven development:
class Customer
def valid?
true
end
end
You now have a method called valid and your test will pass. Obviously it shouldn't always be true so your next step would be to expand the definition of valid?. What check needs to be done to know if a customer is valid or not?

Sinatra unit test - post with JSON body

I am trying to build a unit test for a REST API I built using Sinatra. For right now I just want to test that my echo function works right. Echo uses POST and will return the exact same payload from the post. I am still new with ruby, so forgive me if I don't use the proper lingo.
Here is the code I want to test:
post '/echo' do
request.body.read
end
This is the unit test I am trying to make:
ENV['RACK_ENV'] = 'test'
require './rest_server'
require 'test/unit'
require 'rack/test'
require 'json'
class RestServer < Test::Unit::TestCase
def app
Sinatra::Application
end
def test_check_methods
data = '{"dataIn": "hello"}'
response = post '/echo', JSON.parse(data)
assert.last_response.ok?
assert(response.body == data)
end
end
With the above code, here is the error:
NoMethodError: undefined method `dataIn' for Sinatra::Application:Class
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `block in compile!'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `each_pair'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1285:in `compile!'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1267:in `route'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1256:in `post'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1688:in `block (2 levels) in delegate'
/Users/barrywilliams/RubymineProjects/project/rest_server_test.rb:20:in `test_check_methods'
If I try doing it without the JSON.parse, I get
NoMethodError: undefined method `key?' for "{\"dataIn\": \"hello\"}":String
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1265:in `route'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1256:in `post'
/Users/barrywilliams/.rvm/gems/ruby-1.9.3-p448/gems/sinatra-1.3.4/lib/sinatra/base.rb:1688:in `block (2 levels) in delegate'
/Users/barrywilliams/RubymineProjects/project/rest_server_test.rb:20:in `test_check_methods'
If I try doing it where data = 'hello', then I get the same undefined method 'key?' error
I've tried this suggestion, with no success:
http://softwareblog.morlok.net/2010/12/18/testing-post-with-racktest/
I get an error saying that post only takes 2 arguments, not 3.
So, in summary, I need to be able to make a call, have the code I'm testing receive the call and return a response, then I need to be able to read that response and verify it was the original data. Right now it looks like it's getting stuck at just making the call.
I did a thing a little similar, it might help you :
The application post definition :
post '/' do
data = JSON.parse request.body.read.to_s
"Hello !\n#{data.to_s}"
end
The .to_s is necessary, else the conversions will not be exactly the same :-/
Then on the test file :
class RootPostTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_return_the_parameters
data = {
'reqID' => 1,
'signedReqID' => "plop",
'cert' => "mycert"
}
post '/', data.to_json, "CONTENT_TYPE" => "application/json"
assert last_response.ok?
body_espected = "Hello !\n#{JSON.parse(data.to_json).to_s}"
assert_equal last_response.body, body_espected
end
end
Hope it helped you.
Rack Test will give you back the response body in last_response.body, no need to save it to a variable. You're also not echoing back what you've sent - data in the code you've given is JSON, but you converted it to a hash and posted that, so it's not going to match what comes back. Either send JSON, or convert it to JSON in the Sinatra route if you want to do that (see https://stackoverflow.com/a/12138793/335847 for more).
In the Sinatra app:
require 'json'
post '/echo' do
# Don't use request.body.read as you're not posting JSON
params.to_json
end
and in the test file:
def test_check_methods
data = '{"dataIn": "hello"}'
post '/echo', JSON.parse(data)
assert.last_response.ok?
assert(last_response.body == data)
end
If you do end up wanting to post JSON (which I think is usually not a good idea if it's easy to convert or already have the data as a hash) then use :provides => "json" as a condition to the route, and consider using Rack::Test::Accepts to make life easier writing the test for that (note: that's a shameless plug for a gem I wrote;)

Cannot get stub verification of should_receive to work when stubbing a method on a stub instance

I'm trying to stub the facebook graph api that is wrapped by Koala. My goal is to verify that the graph is initialized with the given access token, and the method "me" is called.
My rspec code looks like:
require 'spec_helper'
describe User do
describe '.new_or_existing_facebook_user' do
it 'should get the users info from facebook using the access token' do
# SETUP
access_token = '231231231321'
# build stub of koala graph that expected get_object with 'me' to be called and return an object with an email
stub_graph = stub(Koala::Facebook::API)
stub_graph.stub(:get_object). with('me'). and_return({
:email => 'jame1231231tl#yahoo.com'
})
# setup initializer to return that stub
Koala::Facebook::API.stub(:new) .with(access_token). and_return(stub_graph)
# TEST
user = User.new_or_existing_facebook_user(access_token)
# SHOULD
stub_graph.should_receive(:get_object).with('me')
end
end
end
Model code looks like:
class User < ActiveRecord::Base
# attributes left out for demo
class << self
def new_or_existing_facebook_user(access_token)
#graph = Koala::Facebook::API.new(access_token)
#me = #graph.get_object('me')
# rest of method left out for demo
end
end
end
When running the test, I get the error:
1) User.new_or_existing_facebook_user should get the users info from facebook using the access token
Failure/Error: stub_graph.should_receive(:get_object).with('me')
(Stub Koala::Facebook::API).get_object("me")
expected: 1 time
received: 0 times
# ./spec/models/user_spec.rb:21:in `block (3 levels) in <top (required)>'
How am I stubbing that method wrong?
The should_receive needs to go before the method is called. Rspec message expectations work by taking over the method and listening to it, very similarly to stub. In fact, you can put it in place of your stub.
The expectation will then decide whether it succeeds or not after the rest of the spec is finished.
Try this instead:
describe User do
describe '.new_or_existing_facebook_user' do
it 'should get the users info from facebook using the access token' do
# SETUP
access_token = '231231231321'
# build stub of koala graph that expected get_object with 'me' to be called and return an object with an email
stub_graph = stub(Koala::Facebook::API)
# SHOULD
stub_graph.should_receive(:get_object).with('me').and_return({
:email => 'jamesmyrtl#yahoo.com'
})
# setup initializer to return that stub
Koala::Facebook::API.stub(:new).with(access_token).and_return(stub_graph)
# TEST
user = User.new_or_existing_facebook_user(access_token)
end
end
end
First off, I would not use stub since stub indicates you are most likely not concerned with the behavior of the object. You should use mock instead even though they instantiate the same thing. This more clearly shows you would like to test its behavior.
Your problem comes from that you are setting the expectation after the test. You need to set the expectation before the test in order to have it register.

payroll_items_controller_spec.rb:18:in `block (2 levels) displayed in Rspec Controller code

Below is the controller code in rspec for a master item.
To be very frank I'm very new to Ruby with a little knowledge of coding.
require 'spec_helper'
describe PayrollItemsController , "with valid params" do
before(:each) do
#payroll_item = mock_model(PayrollItem, :update_attributes => true)
PayrollItem.stub!(:find).with("1").and_return(#payroll_item)
end
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
end
it "should update the PayrollItem object's attributes" do
#payroll_item.should_receive(:update_attributes).and_return(true)
end
end
When I run the controller code, following error displayed:
(Mock "PayrollItem_1001").update_attributes(any args)
expected: 1 time
received: 0 times
./payroll_items_controller_spec.rb:18:in `block (2 levels) in '
You have to actually make a request (get, post, put etc.) to the controller in order for the mock to have anything to check.
So for example:
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
put :update, :id => "0"
end
In addition to that, looking at your code, you have some inconsistencies with your return values: in your before block you're stubbing PayrollItem.find with an id of 1 to return something, and then in your first spec you're mocking it with an id of 0 to return the same thing.
It's fine to both stub and mock the same method because they fulfill different functions: a stub makes sure that the code runs smoothly, while the mock actually checks an expectation. However, you should be stubbing/mocking it for the same argument, so that all the specs using this before block are testing the same thing.

Weird error when trying to test method with argument in Mocha. Is it a bug or is it me?

It's rather hard to find any documentation on Mocha, so I'm afraid I'm totally at sea here. I have found a problem with stubbing methods that pass arguments. So for instance if I set up a class like this:
class Red
def gets(*args)
#input.gets(*args)
end
def puts(*args)
#output.puts(*args)
end
def initialize
#input = $stdin
#output = $stdout
end
private
def first_method
input = gets.chomp
if input == "test"
second_method(input)
end
end
def second_method(value)
puts value
second_method(value)
end
end
Yes it's contrived, but it's a simplification of the idea that you may have a method that you don't want called in the test.
So I might write a test such as:
setup do
#project = Red.new
#project.instance_variable_set(:#input, StringIO.new("test\n"))
#project.stubs(:second_method)
end
should "pass input value to second_method" do
#project.expects(:second_method).with("test").once
#project.instance_eval {first_method}
end
Now I would expect this to pass. But instead I get this rather arcane error message:
Errno::ENOENT: No such file or directory - getcwd
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `expand_path'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `block in filtered'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `reject'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/backtrace_filter.rb:12:in `filtered'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/expectation_error.rb:10:in `initialize'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/mockery.rb:53:in `new'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/mockery.rb:53:in `verify'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/api.rb:156:in `mocha_verify'
/Users/i0n/.rvm/gems/ruby-1.9.2-head/gems/mocha-0.9.8/lib/mocha/integration/mini_test/version_131_and_above.rb:27:in `run'
This means absolutely nothing to me, other than something deep in Mochas bowels has just gone clang. If I write the same sort of test without an argument passing to the second method I get no problem. Am I missing something?
I think it must be something in shoulda causing the problem. I use test/unit, and everything appears to be OK.
require 'rubygems'
require "test/unit"
require 'mocha'
require File.dirname(__FILE__) + '/../src/red'
class RedTest < Test::Unit::TestCase
def setup
#project = Red.new
#project.instance_variable_set(:#input, StringIO.new("test\n"))
#project.stubs(:second_method)
end
def test_description_of_thing_being_tested
#project.expects(:second_method).with("test").once
#project.instance_eval {first_method}
end
end
gives the following output:
stephen#iolanta:~/tmp/red/test # ruby red_test.rb
Loaded suite red_test
Started
.
Finished in 0.000679 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
stephen#iolanta:~/tmp/red/test #
Sorry - I've only just seen this. It's better to submit bug reports to us in Lighthouse. What documentation have you found? Have you seen the RDoc on Rubyforge? What sort of documentation were you looking for that you did not find?
I've been unable to reproduce your bug. What version of Ruby, Rubygems, Shoulda & Mocha were you using?
You can see the results of me running your test in this Gist.

Resources