Why doesn't MiniTest::Spec have a wont_raise assertion?
MiniTest does implement assert_nothing_raised
in its Test::Unit compatibility layer, but in its own tests (MiniTest::Unit
and MiniTest::Spec
) it does not implement any test like this. The reason is, the programmer argues, that testing for nothing raised is not a test of anything; you never expect anything to be raised in a test, except when you are testing for an exception. If an unexpected (uncaught) exception occurs in the code for a test, you'll get an exception reported in good order by the test and you'll know you have a problem.
Example:
require 'minitest/autorun'
describe "something" do
it "does something" do
Ooops
end
end
Output:
Run options: --seed 41521
# Running tests:
E
Finished tests in 0.000729s, 1371.7421 tests/s, 0.0000 assertions/s.
1) Error:
test_0001_does_something(something):
NameError: uninitialized constant Ooops
untitled:5:in `block (2 levels) in <main>'
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Which is exactly what you wanted to know. If you were expecting nothing to be raised, you didn't get it and you've been told so.
So, the argument here is: do not use assert_nothing_raised
! It's just a meaningless crutch. See, for example:
https://github.com/seattlerb/minitest/issues/70
https://github.com/seattlerb/minitest/issues/159
http://blog.zenspider.com/blog/2012/01/assert_nothing_tested.html
On the other hand, clearly assert_nothing_raised
corresponds to some intuition among users, since so many people expect a wont_raise
to go with must_raise
, etc. In particular one would like to focus an assertion on this, not merely a test. Luckily, MiniTest is extremely minimalist and flexible, so if you want to add your own routine, you can. So you can write a method that tests for no exception and returns a known outcome if there is no exception, and now you can assert for that known outcome.
For example (I'm not saying this is perfect, just showing the idea):
class TestMyRequire < MiniTest::Spec
def testForError # pass me a block and I'll tell you if it raised
yield
"ok"
rescue
$!
end
it "blends" do
testForError do
something_or_other
end.must_equal "ok"
end
end
The point is not that this is a good or bad idea but that it was never the responsibility of MiniTest to do it for you.
Why doesn't Minitest's assert_raises work as expected in this case?
This isn't a problem with assert_raises
, that continues to work just fine. The issue you are having is that the exception you are raising within your Rails app is getting handled by your Rails app and not propagating out to your test. Calling get '/missing'
is raising the AbstractController::ActionNotFound
error, but then your app handles the error and returns an appropriate response (404 or 500) to the client (your test).
Okay, so how would you go about testing that your controller really did raise the error you are expecting? Normally you would just use an ActionController::TestCase
test to test your controllers. But, when you call an action on a controller that doesn't in ActionController::TestCase
you get a different error. So the following:
require "test_helper"
class TestControllerTest < ActionController::TestCase
def test_missing
assert_raises AbstractController::ActionNotFound do
get :missing
end
end
end
Produces the following output:
1) Failure:
TestControllerTest#test_missing [test/controllers/test_controller_test.rb:6]:
[AbstractController::ActionNotFound] exception expected, not
Class: <ActionController::UrlGenerationError>
Message: <"No route matches {:action=>\"missing\", :controller=>\"test\"}">
---Backtrace---
test/controllers/test_controller_test.rb:7:in `block in test_missing'
test/controllers/test_controller_test.rb:6:in `test_missing'
---------------
The reason is because ActionController::TestCase
is aware of the routes and won't allow calling an action that is not in the routes. But that is exactly what you are trying to test. And, I assume why you aren't using ActionController::TestCase
.
At this point I wonder if you aren't testing something you shouldn't. Perhaps you should allow Rails to do its job and trust that it behaves correctly. But hey, we've come this far, why not just go all the way and test what Rails tests anyway. Instead of making the call through the full Rails app, let's try calling the TestController
directly. If we create a new instance of the controller we can ask it to handle an action even if that action is not defined on the controller. But to do this we will have to dive deep into how ActionController works and make use of the AbstractController#process
method:
require "test_helper"
class TestControllerTest < Minitest::Test
def test_missing
assert_raises AbstractController::ActionNotFound do
TestController.new.process :missing
end
end
end
Now we are asking the controller to process an action that doesn't exist and testing that the controller behaves as expected. Yay?
What is the expected syntax for checking exception messages in MiniTest's assert_raises/must_raise?
You can use the assert_raises
assertion, or the must_raise
expectation.
it "must raise" do
assert_raises RuntimeError do
bar.do_it
end
-> { bar.do_it }.must_raise RuntimeError
lambda { bar.do_it }.must_raise RuntimeError
proc { bar.do_it }.must_raise RuntimeError
end
If you need to test something on the error object, you can get it from the assertion or expectation like so:
describe "testing the error object" do
it "as an assertion" do
err = assert_raises RuntimeError { bar.do_it }
assert_match /Foo/, err.message
end
it "as an exception" do
err = ->{ bar.do_it }.must_raise RuntimeError
err.message.must_match /Foo/
end
end
How to assert that a Rails method will not throw a NoMethodError
You want to use assert_raise
with the exception class you're expecting:
assert_raise(NoMethodError) { do_a_logout }
ETA: If you want to test that no error was raised, you can use assert_nothing_raised:
assert_nothing_raised { do_a_logout }
assert_nothing_raised(NoMethodError) { do_a_logout }
However as pointed out on SO and elsewhere, there is no good reason to use this assertion, because its redundant: you never expect an error to be raised, unless you specify otherwise.
Moreover, it gives a less helpful failure than simply running the spec without the assertion. In particular, running with assert_nothing_raised
when an error occurs will give you a backtrace only to the assert_nothing_raised
call, while running the spec without this assertion will give a backtrace to the actual error that's occurring.
minitest assert_select with capybara click_link
Firstly there is no need to assert on the presence of an element before clicking it since click_link
will wait up to Capybara.default_max_wait_time seconds for the link to appear on the page and then click it. If the link doesn't appear in that time it will raise an error, so asserting on its presence is superfluous.
The error you're getting is because minitest-capybara isn't compatible with Capybara 2.9+ - https://github.com/wojtekmach/minitest-capybara/pull/17 - and the fact that assert_select
doesn't take 2 strings as parameters. It just takes the id, name, or label text of a select element. So I'm guessing that's not actually the method you mean to be calling.
How to assert certain method is called with Ruby minitest framework?
With minitest you use expect
method to set the expectation for a method to be called on a mock object like so
obj = MiniTest::Mock.new
obj.expect :right
If you want to set expectation with parameters and return values then:
obj.expect :right, return_value, parameters
And for the concrete object like so:
obj = SomeClass.new
assert_send([obj, :right, *parameters])
How to test if method gets called using MiniTest::Mock
You could do something like this:
def test_if_bar_method_calls_puts
mock = MiniTest::Mock.new
mock.expect(:puts, nil, ['bar'])
@class.stub :puts, -> (arg) { mock.puts arg } do
@class.bar
end
assert mock.verify
end
Related Topics
Database Configuration Does Not Specify Adapter
Using a String as a Variable at Run Time
Confusion About Passing Instance Variables to Redirect_To Method. as Seen in Rails Guides
String#Encode Not Fixing "Invalid Byte Sequence in Utf-8" Error
Mocha Mock Carries to Another Test
Does Ruby Provide a Way to Do File.Read() with Specified Encoding
Rails - Cannot Run App: Unable to Load the Eventmachine C Extension;
Understanding the Gemfile.Lock File
Ruby on Rails: How to Run Things in the Background
What Does 'Def Self.Function' Name Mean
"Whenever" Gem Running Cron Jobs on Heroku
Using Custom To_JSON Method in Nested Objects
How to Make Part of a Regular Expression Optional in Ruby
Activerecord::Adapternotspecified Database Configuration Does Not Specify Adapter
Ruby SQLite3 Installation SQLite3_Libversion_Number() MACos Sierra