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
Related
This question tells me how to test logger statements from RSpec model and controller specs. Unfortunately, it doesn't seem to work from a feature spec. With this code:
# controller.rb
def action
logger.info 'foobar'
end
# spec.rb
scenario 'logging should work' do
expect(Rails.logger).to receive(:info).with('foobar')
visit action_path
end
I get the error:
Failure/Error: visit action_path
#<ActiveSupport::Logger:0x007ff45b6e5ad0> received :info with unexpected arguments
expected: ("foobar")
got: (no args)
The test.log file does not contain foobar, so it seems the test is failing immediately, before the controller action has a chance to complete.
Is there some way to use this expect(Rails.logger) syntax in a feature spec?
The Rails.logger.info method can take a string or a block. If you're invoking the block form then it will give this "got: (no args)" output.
For example
logger.info 'foobar'
...all on one line will call .info with a string, but if you do
logger.info
"foobar foobar longer message so I'll put it on its own line"
split across two lines without brackets, then you're actually passing a block. Add some brackets...
logger.info(
"foobar foobar longer message so I'll put it on its own line"
)
...and you're back to a string.
He says knowingly after bashing his head on this problem for a few hours :-)
Before realising that, I started figuring out how to mock the Rails.logger class. That might be a useful approach for you or others. Maybe you're calling with a block for some other reason (something to do with feature vs controller specs?), or maybe you can't change the calling code, in which case... something like this might be a useful starting point:
class LoggerMock < Object
def initialize; end
def info(progname = nil, &block)
mock_info(block.call)
end
end
and
logger_mock = LoggerMock.new
allow(Rails).to receive(:logger).and_return(logger_mock)
expect(logger_mock).to receive(:mock_info).with('foobar')
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
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
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
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)