What's the proper way to force an RSpec test to fail?
I'm considering 1.should == 2 however there's probably something better.
fail/raise will do the trick (they are aliases of each other).
Example
specify "this test fails" do
raise "this is my failure message"
end
Fails with:
1) failing this test fails
Failure/Error: raise "this is my failure message"
RuntimeError:
this is my failure message
Alternatives
If you are thinking of using raise/fail in a spec, you should consider that there are probably more explicit ways of writing your expectation.
Additionally, raise/fail doesn't play well with aggregate_failures because the exception short-circuits the block and won't run any following matchers.
Mark a test as pending
If you need to mark a test as pending to make sure you get back to it, you could use the fail/raise, but you can also use pending.
# 🚫 Instead of this:
it "should do something" do
# ...
raise "this needs to be implemented"
end
# ✅ Try this:
it "should do something" do
pending "this needs to be implemented"
end
Assert that a block is not called
If you need to ensure a block is not being executed, consider using the yield matchers. For example:
describe "Enumerable#any?" do
# 🚫 Instead of this:
it "doesn't yield to the block if the collection is empty" do
[].any? { raise "it should not call this block" }
end
# ✅ Try this:
it "doesn't yield to the block if the collection is empty" do
expect { |b| [].any?(&b) }.not_to yield_control
end
end
I know that this was asked and answered many years ago, but RSpec::ExampleGroups has a flunk method. I prefer this flunk method to using fail in the context of testing. Using fail has an implied code failure (you can see more on that here: https://stackoverflow.com/a/43424847/550454).
So, you can use:
it 'is expected to fail the test' do
flunk 'explicitly flunking the test'
end
If you want to simulate an RSpec expectation failure rather than an exception, the method you're looking for is RSpec::Expectations.fail_with:
describe 'something' do
it "doesn't work" do
RSpec::Expectations.fail_with('oops')
end
end
# => oops
#
# 0) something doesn't work
# Failure/Error: RSpec::Expectations.fail_with('oops')
# oops
Note that despite the documentation, fail_with doesn't actually raise an ExpectationNotMetError directly, but rather passes it to the private method RSpec::Support.notify_failure. This is handy when using aggregate_failures, which (under the hood) works via a custom failure notifier.
describe 'some things' do
it "sometimes work" do
aggregate_failures('things') do
(0..3).each do |i|
RSpec::Expectations.fail_with("#{i} is odd") if i.odd?
end
end
end
end
# => some things
#
# Got 2 failures from failure aggregation block "things":
#
# 1) 1 is odd
#
# 2) 3 is odd
#
# 0) some things sometimes work
# Got 2 failures from failure aggregation block "things".
#
# 0.1) Failure/Error: RSpec::Expectations.fail_with("#{i} is odd") if i.odd?
# 1 is odd
#
# 0.2) Failure/Error: RSpec::Expectations.fail_with("#{i} is odd") if i.odd?
# 3 is odd
# sometimes work (FAILED - 1)
Related
I'm using capybara to do some web automation.
There are various points in the code where it says things like
raise_error 'Failed as this is a duplicate' if duplicate?
or
raise_error 'Failed to log in' if logged_in? == false
All of this is abstracted to a module and I'd prefer that module to not rely on anything in the models.
What I'm struggling with is how to access that error text when I'm running it, from outside the model.
i.e.
Class Thing
has_many :notes
def do_something
#done = Module::Task.something(self.attribute)
if #done
self.update_attributes(status:'Done')
else
self.notes.new(text: error.text)
end
end
but I can't work out the syntax to get that error text.
Answer: If I understand you correctly then errors that appeared while completing the task
#done = Module::Task.something(self.attribute)
can be accessed via #done.errors.messages
Example: If I have User model where attribute username has 2 validations: presence and format then error messages display like this:
irb(main):019:0* u = User.new
irb(main):022:0* u.save # wont succeed
irb(main):028:0* u.errors.messages
=> {:uid=>["can't be blank", "is invalid"]}
If you want to test error messages with capybara then you can use the syntax like this:
it 'raises jibberishh' do
expect{User.raise_error_method}.to raise_error("jibberishh")
end
Say I have a test
describe "import stuff"
it "should import correctly"
imported_count = import_stuff()
assert_equal 3, imported_count
end
end
How do I log the database state e.g. puts ImportedStuff.all.to_json when the test fails?
I know I can specify the error message like this assert_equals 3, imported_count, 'my message'. But I don't want to invoke ImportedStuff.all.to_json when the tests succeed. assert_equals seems to only accept a string for the error message.
Although it doesn't appear to be documented you can pass a Proc as a message. The proc passed will be invoked only on failure.
Example:
def print_db_state
Proc.new { puts DimensionType.all.to_json }
end
describe "import stuff"
it "should import correctly"
imported_count = import_stuff()
assert_equal 3, imported_count, print_db_state
end
end
Passing ImportedStuff.all.to_json will invoke all.to_json every time the assertion is executed even if the test doesn't fail - not good.
Minitest prints passed message only if assertion had failed ( https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L127 ). You can pass "ImportedStuff.all.to_json" and don't afraid that it would be calculated for successful assertion.
You could also pass a parameterless Proc to all minitest assertions as message ( instead of String ), it would be called when test fails and it's result would be printed as message.
You usually don't need to log the database status for a test. Tests help you determine if everything is ok, or if there's something that needs your attention. If the test fails, then you can always re-run it adding a log for the database.
If you want the log to be there, I'd say to add an if:
imported_count = import_stuff()
if imported_count==3
puts ImportedStuff.all.to_json
end
assert_equal 3, imported_count
Another option you have, is to put the logging in an after block.
EDIT
If you want to log your errors if your test fails, you can add:
def teardown
unless passed?
puts ImportedStuff.all.to_json
end
end
I have a test that looks like this:
class PageTest < ActiveSupport::TestCase
describe "test" do
test "should not save without attributes" do
page = Page.new
assert !page.save
end
end
end
When running the tests, I get 0 tests, 0 assertions. If I remove the describe "test" do, I get the 1 test, 1 assertions. So I have the feeling that the describe "..." do is actually making the test disappear.
What is going on here? What am I missing?
Looks like you're mixing up minitest specs and ActiveSupport::TestCase. If you check the rails guides on testing the test method is explained but it's not used with describe.
Rails adds a test method that takes a test name and a block. It
generates a normal MiniTest::Unit test with method names prefixed with
test_. So,
test "the truth" do
assert true
end
acts as if you had written
def test_the_truth
assert true
end
The describe syntax is explained in the minitest docs under the spec section and is used with it (and not test). Like so:
describe "when asked about cheeseburgers" do
it "must respond positively" do
#meme.i_can_has_cheezburger?.must_equal "OHAI!"
end
end
So based on my understanding, I beleive when you do
Resque.inline = Rails.env.test?
Your resque tasks will run synchronously. I am writing a test on resque task that gets enqueue during an after_commit callback.
after_commit :enqueue_several_jobs
#class PingsEvent < ActiveRecord::Base
...
def enqueue_several_jobs
Resque.enqueue(PingFacebook, self.id)
Resque.enqueue(PingTwitter, self.id)
Resque.enqueue(PingPinterest, self.id)
end
In the .perform methd of my Resque task class, I am doing a Rails.logger.info and in my test, I am doing something like
..
Rails.logger.should_receive(:info).with("PingFacebook sent with id #{dummy_event.id}")
PingsEvent.create(params)
And I have the same test for PingTwitter and PingPinterest.
I am getting failure on the 2nd and third expectation because it seems like the tests actually finish before all the resque jobs get run. Only the first test actually passes. RSpec then throws a MockExpectationError telling me that Rails.logger did not receive .info for the other two tests. Anyone has had experience with this before?
EDIT
Someone mentioned that should_receive acts like a mock and that I should do .exactly(n).times instead. Sorry for not making this clear earlier, but I have my expectations in different it blocks and I don't think a should_receive in one it block will mock it for the next it block? Let me know if i'm wrong about this.
class A
def bar(arg)
end
def foo
bar("baz")
bar("quux")
end
end
describe "A" do
let(:a) { A.new }
it "Example 1" do
a.should_receive(:bar).with("baz")
a.foo # fails 'undefined method bar'
end
it "Example 2" do
a.should_receive(:bar).with("quux")
a.foo # fails 'received :bar with unexpected arguments
end
it "Example 3" do
a.should_receive(:bar).with("baz")
a.should_receive(:bar).with("quux")
a.foo # passes
end
it "Example 4" do
a.should_receive(:bar).with(any_args()).once
a.should_receive(:bar).with("quux")
a.foo # passes
end
end
Like a stub, a message expectation replaces the implementation of the method. After the expectation is fulfilled, the object will not respond to the method call again -- this results in 'undefined method' (as in Example 1).
Example 2 shows what happens when the expectation fails because the argument is incorrect.
Example 3 shows how to stub multiple invocations of the same method -- stub out each call with the correct arguments in the order they are received.
Example 4 shows that you can reduce this coupling somewhat with the any_args() helper.
Using should_receive behaves like a mock. Having multiple expectations on the same object with different arguments won't work. If you change the expectation to Rails.logger.should_receive(:info).exactly(3).times your spec will probably past.
All that said, you may want to assert something more pertinent than what is being logged for these specs, and then you could have multiple targeted expectations.
The Rails.logger does not get torn down between specs, so it doesn't matter if the expectations are in different examples. Spitting out the logger's object id for two separate examples illustrates this:
it 'does not tear down rails logger' do
puts Rails.logger.object_id # 70362221063740
end
it 'really does not' do
puts Rails.logger.object_id # 70362221063740
end
I have a test more or less like this:
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
# ...
I've purposely added a
raise "blah"
somewhere down the road and I get this error:
RuntimeError: blah
test/unit/form_definition_test.rb:79:in `__bind_1290079321_362430'
when I should be getting something along:
/Users/pupeno/projectx/db/seed/sheet_definitions.rb:17:in `sheet_definition': blah (RuntimeError)
from /Users/pupeno/projectx/db/seed/form_definitions.rb:4:in `form_definition'
from /Users/pupeno/projectx/test/unit/form_definition_test.rb:79
Any ideas what is sanitizing/destroying my backtraces? My suspicious is shoulda because the when the exception happens inside a setup or should is whet it happens.
This is a Rails 3 project, in case that's important.
That is because the shoulda method #context is generating code for you. for each #should block it generates a completely separate test for you so e.g.
class FormDefinitionTest < ActiveSupport::TestCase
context "a form_definition" do
setup do
#definition = SeedData.form_definition
end
should "verify some condition" do
assert something
end
should "verify some other condition" do
assert something_else
end
end
end
Then #should will generate two completely independent tests (for the two invocations of #should), one that executes
#definition = SeedData.form_definition
assert something
and another one that executes
#definition = SeedData.form_definition
assert something_else
It is worth noting that it does not generate one single test executing all three steps in a sequence.
These generated blocks of codes have method names like _bind_ something and the generated test have name that is a concatenation of all names of the contexts traversed to the should block plus the string provided by the should block (prefixed with "should "). There is another example in the documentation for shoulda-context.
I think this will give you the backtrace that you want. I haven't tested it, but it should work:
def exclude_backtrace_from_location(location)
begin
yeild
rescue => e
puts "Error of type #{e.class} with message: #{e.to_s}.\nBacktrace:"
back=e.backtrace
back.delete_if {|b| b~=/\A#{location}.+/}
puts back
end
end
exclude_backrace_from_location("test/unit") do
#some shoulda code that raises...
end
Have you checked config/initializers/backtrace_silencers.rb? That is the entry point to customize that behavior. With Rails.backtrace_cleaner.remove_silencers! you can cleanup the silencers stack.
More informations about ActiveSupport::BacktraceCleaner can be found here.