Padrino rspec controller testing always green - ruby

I am testing my controller with rspec on padrino with this code:
https://gist.github.com/anonymous/8d0df4c189e99c7cb7ea
If I run the test everythings goes fine and all the test will be green.
The problem is that those test must fail! The sign_in_admin on the before block doesn't allow the user to login and make the post call and also if I change the line
last_response.should_not be_ok
with
last_response.should be_ok
the test is always green.....
I don't know where I am wrong.
Here is my spec_helper.rb
https://gist.github.com/anonymous/6442d02654cbee2cf3b5

The reason your code is always passing is because your test is of the form:
lambda {}.should {}
which is equivalent to
lambda {}.should
since should ignores any block passed to it. That is further equivalent to:
lambda {}.should be_truthy
which always succeeds because lambda {} is a Proc, which is truthy.
You should only send should to a Proc if you want should to execute the proc for purposes of evaluating side effects (e.g. raising errors, interacting with other objects), for which there are matchers of the form raise_error and change. The same is true for passing a block to expect, which is the current syntax.
In your case, you can simply execute the code that in your current lambda expression and then check the value of last_response, as in:
it '...' do
post ...
last_response.should be_ok
end
or the more current:
it '...' do
post ...
expect(last_response).to be_ok
end

Related

RSpec: Expecting a method to be called causes that method to not actually be called

I have some code that could be represented in very simple terms as:
def method_a(key)
hash = method b(key)
hash.delete(key)
end
def method_b(key)
return { key => 1 }
end
and then an rspec test like
it 'calls method_b'
expect(someClass).to receive(:method_b).with(key)
method_a(key)
end
However I then get an error in the second line of method_a because it's trying to call delete on a nil object. When I debug, I can see that the logic inside method_b is never actually being invoked. It's not failing somewhere in method_b, it's literally not calling it at all. If I get rid of the expect statement in the test, this error goes away. It seems like the expect statement is causing it to just skip over the actual call to method_b, leaving me with a nil value instead of the hash I'm expecting.
Is there a way I can stop it from skipping over method_b, or at least terminate execution once the expect statement is successful, so I don't run into the error on the next line?
When you set a message expectation, it overrides the original code, unless you explicitly tell RSpec not to:
expect(someClass).to receive(:method_b).with(key).and_call_original

Best Practices for RSpec expect raise_error

I have some code for RSpec.
My (or not my) Class can (or not) call specific exception.
Ex, Class::Foo::Bar::TimeOutError.
I want check it.
describe :describe_description do
before :all do
object = Class.new
# method can be call Class::Foo::Bar::TimeOutError
object.method
end
it :exception do
expect {}.to_not raise_error Class::Foo::Bar::TimeOutError
end
end
This code worked, but RSpec write a Warning:
WARNING: Using expect { }.not_to raise_error(SpecificErrorClass) risks false positives, since literally any other error would cause the expectation to pass, including those raised by Ruby (e.g. NoMethodError, NameError and ArgumentError), meaning the code you are intending to test may not even get reached. Instead consider using expect {}.not_to raise_error or expect { }.to raise_error(DifferentSpecificErrorClass). This message can be suppressed by setting: RSpec::Expectations.configuration.on_potential_false_positives = :nothing.
How can I fix my code so that the RSpec works correctly and does not issue warnings?
No, there isn't a way to apply expect {}.to_not raise_error in the spec without this warning (that's the whole point of adding this warning).
It doesn't make sense logically because you don't need to explicitly check a type of error is not raised.
Scenario 1. The code executes successfully (i.e no errors at all). Then a passing test itself implies there's no Class::Foo::Bar::TimeOutError thrown.
Scenario 2. Another type of error is thrown. Then expect {}.to_not raise_error Class::Foo::Bar::TimeOutError isn't testing anything, it still just passes which creates false positive.
Scenario 3. If you really want to use it, suppress with the setting on_potential_false_positives then the test will continue without the warning. If it passes, then this has no difference with the scenario which makes expect {}.to_not raise_error redundant anyway.
For a future-proof solution I recommend setting
RSpec::Expectations.configuration.on_potential_false_positives = :raise
And fixing any offending examples from
expect { some_call }.to not_raise_error(SomeErrorClass)
to
expect { some_call }.to not_raise_error
# OR
expect { some_call }.to raise_error(SomeOtherErrorClass)

Is there any way to set an RSpec expectation to receive a method call as an argument?

Ie
expect(my_class).to receive(:method_b).with(:calling_method_b_here)
(it's not the return value I want, but specifically to test that method_b is called in this way)
?
This should work for you if I understand correctly:
it "should call #method_b when I call #method_that_calls_b" do
expect(my_class).to receive(:method_b).with(anything) #anything means I don't care explictly what is sent in just that it was called
my_class.method_that_calls_b
end
This will test that method_that_calls_b actually does call method_b with any arguments. If you know what arguments it is supposed to receive then replace anything with what you expect it to be called with. e.g.
it "should call #method_b with 'hello'" do
expect(my_class).to receive(:method_b).with('hello')
my_class.method_b('goodbye') #this will fail
my_class.method_b('hello') #this will pass without the above line
end

how to reset expectations on a mocked class method?

Sorry if this is plain simple. i am new to ruby as well as rspec and it seems rspec is a very 'obscure' world (esp when coming from a .net background).
In my 'spec', i have:
before(:each) do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
end
This works fine for all my 'examples', except one where i want it to return false.
expect(File).to receive(:exist?).with("non_existent.yaml").and_return (false)
This obviously fails my test because although "non_existent.yaml" expectation was met, the "dummy.yaml" was not:
(<File (class)>).exist?("dummy.yaml")
expected: 1 time with arguments: ("dummy.yaml")
received: 0 times
So how can i do a 'Reset' on 'File.exist?' (a class method mock) before i setup the new expectation for it? (... "non_existent.yaml"..)
i googled and it yielded:
RSpec::Mocks.proxy_for(your_object).reset
but this gives me:
NoMethodError:
undefined method `proxy_for' for RSpec::Mocks:Module
I could not find anywhere in the documentation that this is how you should do it, and past behaviors goes to show that this solution might also change in the future, but apparently this is how you can currently do it:
RSpec::Mocks.space.proxy_for(your_object).reset
I would follow #BroiSatse's remark, though, and think about re-designing the tests, aiming to move the expectation from the before block. The before block is meant for setup, as you say, and the setup is a very weird place to put expectations.
I'm not sure how you came to this design, but I can suggest two possible alternatives:
If the test is trivial, and will work anyway, you should create one test with this explicit expectation, while stubbing it for the other tests:
before(:each) do
allow(File).to receive(:exist?).with("dummy.yaml").and_return (true)
end
it "asks if file exists" do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
# do the test...
end
If the expectation should run for every test, since what changes in each scenario is the context, you should consider using shared examples:
shared_examples "looking for dummy.yaml" do
it "asks if file exists" do
expect(File).to receive(:exist?).with("dummy.yaml").and_return (true)
# do the test...
end
end
it_behaves_like "looking for dummy.yaml" do
let(:scenario) { "something which sets the context"}
end
You might also want to ask myron if there is a more recommended/documented solution to reset mocked objects...
This worked for me to unmock a specific method from a class:
mock = RSpec::Mocks.space.proxy_for(MyClass)
mock.instance_variable_get(:#method_doubles)[:my_method].reset
Note: Same logic of
RSpec::Mocks.space.proxy_for(MyClass).reset which resets all methods
Expanding on #Uri Agassi's answer and as I answered on another similar question, I found that I could use RSpec::Mocks.space.registered? to check if a method was a mock, and RSpec::Mocks.space.proxy_for(my_mocked_var).reset to reset it's value.
Here is the example I included in my other answer:
Example: Resetting a mocked value
For example, if we wanted to reset this mock back to it's unmocked
default value, we can use the RSpec::Mocks.space.proxy_for helper to
find our mock, then reset it:
# when
# Rails.configuration.action_controller.allow_forgery_protection == false
# and
# allow(Rails.configuration.action_controller).to receive(:allow_forgery_protection).and_return(true)
RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
Rails.configuration.action_controller.allow_forgery_protection
# => true
RSpec::Mocks.space.proxy_for(Rails.configuration.action_controller).reset
Rails.configuration.action_controller.allow_forgery_protection
# => false
Notice however that the even though the mock value has been reset, the
mock remains registered?:
RSpec::Mocks.space.registered?(Rails.configuration.action_controller)
# => true
When using "expect_any_instance" I had success using the following method to change the mock (e.g. our example: Putting out a Twitter post and returning a different tweet id)
expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12"))
# post tweet
RSpec::Mocks.space.verify_all
RSpec::Mocks.space.reset_all
expect_any_instance_of(Twitter::REST::Client).to receive(:update).and_return(Hashie::Mash.new(id: "12346"))
# post another tweet

Is there a way to get a stack trace from rspec when a method is unexpectedly called more times than specified?

I setup a mock object and told it to expect a check for nil and to return false:
status = double('status')
status.should_receive(:nil?).and_return(false)
I only expect the call to nil? to occur once, but I got an error in my rspec test, saying that status received nil? twice.
Is there a way to get rspec to show where/how each call occurred?
adding the '--backtrace' option did not work.
Try something like this:
status.should_receive(:nil?).twice { puts caller; false }
This tells rspec to allow two invocations and call the associated block each time. Thecaller method generates a full backtrace which you should be able to analyze onstdout. We also returnfalse to stay on the code-path we're testing.
If the two backtraces are hard to distinguish and you're only interested in the second (unexpected) invocation, then set up two successive expectations:
status.should_receive(:nil?).and_return(false)
status.should_receive(:nil?) { puts caller; false }
Here the double will return false on the first invocation and call the block on the second.
Reference for setting responses on expectations:
https://github.com/rspec/rspec-mocks#setting-responses

Resources