Capture the outcome of an assert statement? - ruby-on-rails

I am writing a test helper and it has this method:
def todo msg = ''
assert false, '#TODO: ' + msg
end
Basically I want a quick method to fail a test. Next, I would like to test this method (because it will be encapsulated in a shippable helper). So I would like to write something like this:
test 'todo' do
result = todo
expected = '#TODO: '
assert_equal expected, result
end
but it just fails when assert false gets called. So, how would I test this method?

This helper is supposed to work with Test::Unit, is that right? I believe that assert false throws an exception which is caught by Test::Unit. You could just wrap the call to todo in a begin...rescue block and catch that exception.
The test/unit which comes in the Ruby standard libraries is actually Minitest::Unit -- when an assertion fails, it raises a Minitest::Assertion exception. You could rescue that.
For the "real" Test::Unit, it raises a Test::Unit::AssertionFailedError when an assertion fails.

Related

RSpec to test raise exception in rails

I am new to RSpec. I have a method in my model user_profile.rb
def self.create_from_supplement(device, structure)
xml = Nokogiri.parse(structure.to_s)
user_profile = nil
auth_type = xml.%('auth_supplement/auth_type').inner_html
if 'user' == auth_type
user_details_str = xml.%('auth_supplement/supplement_data/content').inner_html rescue nil
return nil if user_details_str.blank?
user_details_xml = Nokogiri.parse(user_details_str)
user_name = user_details_xml.%('username').inner_html
user_profile = UserProfile.find_or_initialize_by(name: user_name)
if user_profile.save
device.update_attributes(user_profile_id: user_profile.id)
else
raise "User Profile Creation Failed because of #{user_profile.errors.full_messages}"
end
end
return user_profile
end
I am writing a unit test case to test when user_profile.save fails, the test case will expect an exception was raised. But in my user_profiles table I have only one column :name.
How to test the case when user_profile.save fails? The most important problem here is I dont find any way to make this user_profile.save to fail.
Some suggests using RSpec Stubs. How do we do that?
With Rspec expectations you have a special syntax for when you expect an error to be raised.
if you did something like this:
expect(raise NoMethodError).to raise_error(NoMethodError)
that wouldn't work - RSpec would not handle the error and would exit.
However if you use brackets:
expect { raise NoMethodError }.to raise_error(NoMethodError)
that should pass.
If you use brackets ( or a do / end block ) than any errors in the block will be 'captured' and you can check them with the raise_error matcher.
checkout rspec documents:
https://www.relishapp.com/rspec/rspec-expectations/v/2-11/docs/built-in-matchers/raise-error-matcher
describe ':: create_from_supplement' do
it 'blows up' do
expect { UserProfile.create_from_supplement(*args) }.to raise_error(/User Profile Creation Failed because of/)
end
end
Tracing back you code, here are the places that might cause the error, and following what you can consider.
user_details_str = xml.%('auth_supplement/supplement_data/content').inner_html
Here user_details_str might be an invalid string format (not nil) because whatever you get from 'auth_supplement/supplement_data/content' is not a correct format.
user_details_xml = Nokogiri.parse(user_details_str)
Here you need to determine what might cause Nokogiri::parse to give you an invalid result.
user_name = user_details_xml.%('username').inner_html
Then here, same as above.
user_profile = UserProfile.find_or_initialize_by(name: user_name)
So here, you might have an invalid user_name due to the previous few lines of code, which violates any validation you might have (e.g. too short, not capitalized, or what not).
More Info
So this can go deeper into your code. It's hard to test because your method is trying to do too much. And this clearly violates abc size (there are too many logical branches, more info here: http://wiki.c2.com/?AbcMetric)
I suggest refactoring some branches of this method into smaller single responsibility methods.

Rspec validation error causing a test to fail (but it's my intention to have a validation error)

Rspec
context "has non ascii characters" do
it "will not call get_imdb" do
expect_any_instance_of(Celebrity).not_to receive(:get_imdb)
FactoryGirl.build(:imdb_celebrity, first_name: "Sæthy")
end
end
model:
def celebrity_status
if full_name.ascii_only?
get_imdb ## << -- returns string or nil
end
### If get_imdb returns nil (or isn't called), record is not valid
end
Validation fails if get_imdb returns nil or isn't called. My problem is that I'm trying to test the ascii characters portion of the method, but by doing that - my validation is failing and giving me a "Validation Failed" error in the console when running Rspec tests.
But that's what I want to happen... I want the validation to fail.
How can I solve this?
By itself, a model validation failure should not raise an exception. You may be attempting to save the record, which would case save to fail, and might raise an exception if called via save! or create!.
You can test whether the model is valid by calling valid?
expect(celebrity.valid?).to be false
It is also useful to check the error hash to see that it has the expected contents:
celebrity.valid? # => false
expect(celebrity.errors[:my_error]).to equal "my error message"
If you want to test your code on having exceptions, you should use Rspec's raise_errormatcher:
expect { 3 / 0 }.to raise_exception
More info here: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/raise-error-matcher

Rspec: How to mute internal exception?

I want to check some internal behaviour of method #abc which also raises an error.
def abc
String.class
raise StandardError
end
describe '#abc' do
it 'should call String.class' do
String.should_receive(:class)
end
end
String.class - is just an example of any method call of any class which I want to perform inside this method.
But I got an error:
Failure/Error: #abc
StandardError
How I can mute this exception so this spec would pass?
You cannot "mute" the exception; you can only catch it, either explicitly through a rescue clause or implicitly through an expectation. Note that this is different than substituting a test double for a called method. The exception is still getting raised by the code under test.
If you don't care whether an error is raised, then you can use:
abc rescue nil
to invoke the method. (Note: This will implicitly only catch StandardError)
If you want to using the should or expect syntax, you need to place the code which is going to raise the error within a block, as in the following:
expect {abc}.to raise_error(StandardError)
Combining this with the setting of an expectation that String.class be invoked, you get:
describe '#abc' do
it 'should call String.class' do
expect(String).to receive(:class)
expect {abc}.to raise_error(StandardError)
end
end

Stubbing an exception, but evaluating normally (RSpec)

I'm trying to update an instance variable #status on an object based on the performance of a block. This block also makes calls to another class.
def run
#entries.keep_if { |i| valid_entry?(i) }.each do |e|
begin
unique_id = get_uniqueid e
cdr_record = Cdr.find_by_uniqueid(unique_id).first
recording = cdr_record.nil? ? NullAsteriskRecording.new : AsteriskRecording.new(cdr_record, e)
recording.set_attributes
recording.import
rescue Exception => e
fail_status
end
end
end
fail_status is a private method that updates the instance variable to :failed. Through breaking some other things, I've basically verified this code works, but I want a test in place as well. Currently, I've got the following:
context "in which an exception is thrown" do
before do
#recording = double("asterisk_recording")
#recording.stub(:import).and_raise("error")
end
it "should set #status to :failed" do
# pending "Update instance variable in rescue block(s) of #run"
subject.run
subject.status.should eq :failed
end
end
But the test always fails. The rescue block is never evaluated (I checked with a puts statement that would be evaluated when I hardcoded in a raise statement). Am I using the double feature wrong, here? Or am I doing myself in by stubbing out an exception, so the rescue block never gets run?
You set up #recording in your before block, but the code you have posted for your run method will not use that #recording instance and therefore the call to recording.import in the run method will not raise an exception.
In your run method, recording can either end up being an instance of NullAsteriskRecording or AsteriskRecording. If you know that it is going to be an AsteriskRecording as your current test implies, one approach would be to change your before block to the following:
before do
AsteriskRecording.any_instance.stub(:import).and_raise("error")
end

Rails ActiveSupport: How to assert that an error is raised?

I am wanting to test a function on one of my models that throws specific errors. The function looks something like this:
def merge(release_to_delete)
raise "Can't merge a release with itself!" if( self.id == release_to_delete.id )
raise "Can only merge releases by the same artist" if( self.artist != release_to_delete.artist )
#actual merge code here
end
Now I want to do an assert that when I call this function with a parameter that causes each of those exceptions, that the exceptions actually get thrown. I was looking at ActiveSupport documentation, but I wasn't finding anything promising. Any ideas?
So unit testing isn't really in activesupport. Ruby comes with a typical xunit framework in the standard libs (Test::Unit in ruby 1.8.x, MiniTest in ruby 1.9), and the stuff in activesupport just adds some stuff to it.
If you are using Test::Unit/MiniTest
assert_raise(Exception) { whatever.merge }
if you are using rspec (unfortunately poorly documented, but way more popular)
lambda { whatever.merge }.should raise_error
If you want to check the raised Exception:
exception = assert_raises(Exception) { whatever.merge }
assert_equal( "message", exception.message )
To ensure that no exception is raised (or is successfully handled) do inside your test case:
assert_nothing_raised RuntimeError do
whatever.merge
end
To check that error is raised do inside your test case:
assert_raise RuntimeError do
whatever.merge
end
Just a heads up, whatever.merge is the code that raises the error (or doesn't, depending on the assertion type).

Resources