New to ruby and trying to fix the error from rspec - ruby

Hi I need to know how to do the following
rspec code:
2) WebServer::Htaccess#authorized? for valid-user with valid credentials returns true
Failure/Error: expect(htaccess_valid_user.authorized?(encrypted_string)).to be_true
ArgumentError:
wrong number of arguments calling `authorized?` (1 for 0)
# ./spec/lib/config/htaccess_spec.rb:82:in `(root)'
# ./spec/lib/config/htaccess_spec.rb:44:in `stub_htpwd_file'
# ./spec/lib/config/htaccess_spec.rb:41:in `stub_htpwd_file'
# ./spec/lib/config/htaccess_spec.rb:40:in `stub_htpwd_file'
# ./spec/lib/config/htaccess_spec.rb:81:in `(root)'
Here is the spec.rb file
let(:htaccess_valid_user) { WebServer::Htaccess.new(valid_user_content) }
let(:htaccess_user) { WebServer::Htaccess.new(user_content) }
describe '#authorized?' do
context 'for valid-user' do
context 'with valid credentials' do
it 'returns true' do
stub_htpwd_file do
expect(htaccess_valid_user.authorized?(encrypted_string)).to be_true
end
end
end
context 'with invalid credentials' do
it 'returns false' do
stub_htpwd_file do
expect(htaccess_valid_user.authorized?(encrypted_string('bad user'))).not_to be_nil
expect(htaccess_valid_user.authorized?(encrypted_string('bad user'))).to be_false
end
end
end
end
I am new to ruby TDD, and all I have in my file right now is
def authorized?
end
I am fluent in Node.js but this is completely new to me.
Please help.

It's right there in the error message.
ArgumentError:
wrong number of arguments calling `authorized?` (1 for 0)
You've passed arguments to the authorized? method.
expect(htaccess_valid_user.authorized?(encrypted_string)).to be_true
^^^^^^^^^^^^^^^^^^
But authorized? takes no arguments.
def authorized?
end
Unlike Javascript, Ruby will check you passed in the right number of arguments. If you specify no argument list, the default is to enforce taking no arguments. Add some.
def authorized?(authorization)
end

Related

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

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.

Rspec: How do I make my passing tests fail?

I have a client object in a ruby gem that needs to work with a web service. I am testing to verify that it can be properly initialised and throws an error if all arguments are not passed in.
Here are my specs:
describe 'Contentstack::Client Configuration' do
describe ":access_token" do
it "is required" do
expect { create_client(access_token: nil) }.to raise_error(ArgumentError)
end
end
describe ":access_key" do
it "is required" do
expect { create_client(access_key: nil) }.to raise_error(ArgumentError)
end
end
describe ":environment" do
it "is required" do
expect { create_client(environment: nil) }.to raise_error(ArgumentError)
end
end
end
Here is the gem code:
module Contentstack
class Client
attr_reader :access_key, :access_token, :environment
def initialize(access_key:, access_token:, environment:)
#access_key = access_key
#access_token = access_token
#environment = environment
validate_configuration!
end
def validate_configuration!
fail(ArgumentError, "You must specify an access_key") if access_key.nil?
fail(ArgumentError, "You must specify an access_token") if access_token.nil?
fail(ArgumentError, "You must specify an environment") if environment.nil?
end
end
end
and here is the spec_helper method:
def create_client(access_token:, access_key:, environment:)
Contentstack::Client.new(access_token: access_token, access_key: access_key, environment: environment)
end
The problem is: I can't find a way to make these tests fail before they pass. These tests always pass because ruby throws an ArgumentError by default. I don't understand if this is the right approach to TDD. How do I get into a red-green-refactor cycle with this scenario?
create_client raises the ArgumentError, because it expects three keyword arguments and you are passing only one: (maybe you should have tested your helper, too)
def create_client(access_token:, access_key:, environment:)
# intentionally left empty
end
create_client(access_key: nil)
# in `create_client': missing keywords: access_token, environment (ArgumentError)
You could use default values in your helper to overcome this:
def create_client(access_token: :foo, access_key: :bar, environment: :baz)
Contentstack::Client.new(access_token: access_token, access_key: access_key, environment: environment)
end
create_client(access_key: nil)
# in `validate_configuration!': You must specify an access_key (ArgumentError)
Finally, you could be more specific regarding the error message:
expect { ... }.to raise_error(ArgumentError, 'You must specify an access_key')
please refer to the answer by Stefan, it’s way more proper
The proper way would be to mock Client#validate_configuration! to do nothing, but here it might be even simpler. Put in your test_helper.rb:
Client.prepend(Module.new do
def validate_configuration!; end
end)
Frankly, I do not see any reason to force tests to fail before they pass in this particular case. To follow the TDD, you should have been running tests before validate_configuration! implementation. Then those tests would have failed.
But since you have implemented it in advance, there is no need to blindly thoughtless follow the rule “test must fail before pass.”

Rspec: expect vs expect with block - what's the difference?

Just learning rspec syntax and I noticed that this code works:
context "given a bad list of players" do
let(:bad_players) { {} }
it "fails to create given a bad player list" do
expect{ Team.new("Random", bad_players) }.to raise_error
end
end
But this code doesn't:
context "given a bad list of players" do
let(:bad_players) { {} }
it "fails to create given a bad player list" do
expect( Team.new("Random", bad_players) ).to raise_error
end
end
It gives me this error:
Team given a bad list of players fails to create given a bad player list
Failure/Error: expect( Team.new("Random", bad_players) ).to raise_error
Exception:
Exception
# ./lib/team.rb:6:in `initialize'
# ./spec/team_spec.rb:23:in `new'
# ./spec/team_spec.rb:23:in `block (3 levels) in <top (required)>'
My question is:
Why does this happen?
What is the difference between the former and later example exactly in ruby?
I am also looking for rules on when to use one over the other
One more example of the same but inverse results, where this code works:
it "has a list of players" do
expect(Team.new("Random").players).to be_kind_of Array
end
But this code fails
it "has a list of players" do
expect{ Team.new("Random").players }.to be_kind_of Array
end
Error I get in this case is:
Failure/Error: expect{ Team.new("Random").players }.to be_kind_of Array
expected #<Proc:0x007fbbbab29580#/Users/amiterandole/Documents/current/ruby_sandbox/tdd-ruby/spec/team_spec.rb:9> to be a kind of Array
# ./spec/team_spec.rb:9:in `block (2 levels) in <top (required)>'
The class I am testing looks like this:
class Team
attr_reader :name, :players
def initialize(name, players = [])
raise Exception unless players.is_a? Array
#name = name
#players = players
end
end
As has been mentioned:
expect(4).to eq(4)
This is specifically testing the value that you've sent in as the parameter to the method. When you're trying to test for raised errors when you do the same thing:
expect(raise "fail!").to raise_error
Your argument is evaluated immediately and that exception will be thrown and your test will blow up right there.
However, when you use a block (and this is basic ruby), the block contents isn't executed immediately - it's execution is determined by the method you're calling (in this case, the expect method handles when to execute your block):
expect{raise "fail!"}.to raise_error
We can look at an example method that might handle this behavior:
def expect(val=nil)
if block_given?
begin
yield
rescue
puts "Your block raised an error!"
end
else
puts "The value under test is #{val}"
end
end
You can see here that it's the expect method that is manually rescuing your error so that it can test whether or not errors are raised, etc. yield is a ruby method's way of executing whatever block was passed to the method.
In the first case, when you pass a block to expect, the execution of the block doesn't occur until it's time to evaluate the result, at which point the RSpec code can catch any error that are raised and check it against the expectation.
In the second case, the error is raised when the argument to expect is evaluated, so the expect code has no chance to get involved.
As for rules, you pass a block or a Proc if you're trying to test behavior (e.g. raising errors, changing some value). Otherwise, you pass a "conventional" argument, in which case the value of that argument is what is tested.

RSpec test ArgumentError on method with parameters

I'm having problems getting this simple test to pass on RSpec 2.8.
I want to write a simple test for the absence of parameters on a method that requires them (i.e. ArgumentError: wrong number of arguments ('x' for 'y')).
My test is testing a Gem module method like so:
describe "#ip_lookup" do
it "should raise an ArgumentError error if no parameters passed" do
expect(#geolocater.ip_lookup).to raise_error(ArgumentError)
end
end
My gem module code looks like this:
module Geolocater
def ip_lookup(ip_address)
return ip_address
end
end
My spec runs with this output.
Failure/Error: expect(#geolocater.ip_lookup).to raise_error(ArgumentError)
ArgumentError:
wrong number of arguments (0 for 1)
# ./lib/geolocater.rb:4:in `ip_lookup'
# ./spec/geolocater_spec.rb:28:in `block (3 levels) in <top (required)>'
What am I missing here?
You need to pass a block to #expect, not a regular argument:
describe "#ip_lookup" do
it "should raise an ArgumentError error if no parameters passed" do
expect { #geolocater.ip_lookup }.to raise_error(ArgumentError)
end
end

How to spec methods that exit or abort

I have a method being triggered from a CLI that has some logical paths which explicitly exit or abort. I have found that when writing specs for this method, RSpec marks it as failing because the exits are exceptions. Here's a a bare bones example:
def cli_method
if condition
puts "Everything's okay!"
else
puts "GTFO!"
exit
end
end
I can wrap the spec in a lambda with should raise_error(SystemExit), but that disregards any assertions that happen inside the block. To be clear: I'm not testing the exit itself, but the logic that happens before it. How might I go about speccing this type of method?
Simply put your assertions outside of the lambda, for example:
class Foo
attr_accessor :result
def logic_and_exit
#result = :bad_logic
exit
end
end
describe 'Foo#logic_and_exit' do
before(:each) do
#foo = Foo.new
end
it "should set #foo" do
lambda { #foo.logic_and_exit; exit }.should raise_error SystemExit
#foo.result.should == :logics
end
end
When I run rspec, it correctly tells me:
expected: :logics
got: :bad_logic (using ==)
Is there any case where this wouldn't work for you?
EDIT: I added an 'exit' call inside the lambda to hande the case where logic_and_exit doesn't exit.
EDIT2: Even better, just do this in your test:
begin
#foo.logic_and_exit
rescue SystemExit
end
#foo.result.should == :logics
New answer to cover Rspec 3's expect syntax.
Testing the output
Just to test what you actually want (ie. you're not testing the exception, or a value response,) what has been output to STDOUT.
When condition is false
it "has a false condition" do
# NOTE: Set up your condition's parameters to make it false
expect {
begin cli_method
rescue SystemExit
end
}.to output("GTFO").to_stdout # or .to_stderr
end
When condition is true
it "has a true condition" do
# NOTE: Set up your condition's parameters to make it true
expect {
begin cli_method
rescue SystemExit
end
}.to output("Everything's okay!").to_stdout
end
Note that output("String").to_... can accept a Regex eg.
output(/^Everything's okay!$/).to_stdout
It can also capture from stderr eg.
output("GTFO").to_stderr
(Which would be a better place to send it, for the OP's example.)
Testing the Exit
You can separately test that the false condition also raises SystemExit
it "exits when condition is false" do
# NOTE: Set up your condition's parameters to make it false
expect{cli_method}.to raise_error SystemExit
end
it "doesn't exit when condition is true" do
# NOTE: Set up your condition's parameters to make it true
expect{cli_method}.not_to raise_error SystemExit
end
I can wrap the spec in a lambda with should raise_error(SystemExit),
but that disregards any assertions that happen inside the block.
I don't see a difference putting tests inside or outside the lambda. In either case, the failure message is a bit cryptic:
def cli_method(condition)
if condition
puts "OK"
else
puts "GTFO"
exit
end
end
describe "cli_method" do
context "outside lambda" do
# passing
it "writes to STDOUT when condition is false" do
STDOUT.should_receive(:puts).with("GTFO")
lambda {
cli_method(false)
}.should raise_error(SystemExit)
end
# failing
it "does not write to STDOUT when condition is false" do
STDOUT.should_not_receive(:puts).with("GTFO")
lambda {
cli_method(false)
}.should raise_error(SystemExit)
end
end
context "inside lambda" do
# passing
it "writes to STDOUT when condition is false" do
lambda {
STDOUT.should_receive(:puts).with("GTFO")
cli_method(false)
}.should raise_error(SystemExit)
end
# failing
it "does not write to STDOUT when condition is false" do
lambda {
STDOUT.should_not_receive(:puts).with("GTFO")
cli_method(false)
}.should raise_error(SystemExit)
end
end
end
# output
.F.F
Failures:
1) cli_method outside lambda does not write to STDOUT when condition is false
Failure/Error: lambda {
expected SystemExit, got #<RSpec::Mocks::MockExpectationError: (#<IO:0xb28cd8>).puts("GTFO")
expected: 0 times
received: 1 time>
# ./gtfo_spec.rb:23:in `block (3 levels) in <top (required)>'
2) cli_method inside lambda does not write to STDOUT when condition is false
Failure/Error: lambda {
expected SystemExit, got #<RSpec::Mocks::MockExpectationError: (#<IO:0xb28cd8>).puts("GTFO")
expected: 0 times
received: 1 time>
# ./gtfo_spec.rb:39:in `block (3 levels) in <top (required)>'

Resources