Upgraded to a minimum of 5.4.0. It is now necessary * and will * not * work in the Minitest :: Test class, but assert * still works
I am using MiniTest that comes with Ruby 2.1 without issue. I would subclass MiniTest :: Unit: TestCase and create a couple of methods like "test_simple" and it just worked. I would use Expectations and Asserts without any problems.
I have updated Minitest to 5.4.0 using gem. Wherever I use Expectations (musts and wonts) I get a contrived error. An example of a test class.
gem 'minitest'
require "minitest/autorun"
require "rest-client"
require "json"
require "pp"
# require './testcase_addins'
class TestUserKey < Minitest::Test
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
When I run this the assert line goes through without issue, but the must_be line throws this error:
1) Error:
TestUserKey#test_simple:
NoMethodError: undefined method `assert_operator' for nil:NilClass
(eval):4:in `must_be'
user_key_testcase.rb:14:in `test_simple'
The weird part is that nil is: NilClass cannot be nil in error; it is 0. Even I change Fixnum to String, I still get the same error.
If I change the test to the spec test everything works again. So I can't use "Expectations" in "Unit Tests"? IF this is the case, can someone explain why?
source to share
The short answer is that as of 5.4.0 your test class must inherit from MiniTest :: Spec in order to use expectations.
I tested this on a new ubuntu machine with ruby ββ2.1.2 installed via RVM:
rvm install ruby-2.1.2
This code works with ruby ββstock 2.1.2 (there is no set minimum of 5.4.0 minitest, slightly stripped down from your example code above):
#!/usr/bin/env ruby
require 'minitest/unit'
require "minitest/autorun"
class TestUserKey < MiniTest::Unit::TestCase
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
Running this code works fine. To reproduce the above error, install minitest 5.4.0:
gem install minitest -v 5.4.0
Now the code fails with the NoMethodError: undefined method `assert_operator 'for nil: NilClass". You now have both minitest versions:
~/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/minitest/ # stock ruby version of minitest
~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/ # minitest v5.4.0 installed via rubygems
Now that everything is set up, we can delve into what exactly is happening. Expectations are defined by calling infect_an_assertion, for example:
infect_an_assertion :assert_operator, :must_be, :reverse
For 5.4.0, this call happens in ~ / .rvm / gems / ruby-2.1.2 / gems / minitest-5.4.0 / lib / minitest / expect.rb. It's pretty much the same in both versions, it just happens elsewhere.
infect_an_assertion is also roughly the same for both versions. For: must_be this ends this call, which is identical between the two minitest versions:
MiniTest::Spec.current.#{meth}(args.first, self, *args[1..-1])
They do metaprogramming here, a runtime call will look more like this since meth is set to assert_operator:
MiniTest::Spec.current.assert_operator(...)
The important part is MiniTest :: Spec.current. In 5.4.0, this method returns nil, which results in a NoMethodError exception when it tries to call assert_operator to nil.
In stock minitest from ruby ββ2.1.2:
Here MiniTest :: Spec inherits from MiniTest :: Unit :: TestCase. TestCase defines a current method and returns the value that was set in the initialize method. You can see this is all happening around line 1303 ~ / .rvm / rubies / ruby-2.1.2 / lib / ruby ββ/2.1.0/minitest/unit.rb:
def initialize name # :nodoc:
@__name__ = name
@__io__ = nil
@passed = nil
@@current = self # FIX: make thread local
end
def self.current # :nodoc:
@@current # FIX: make thread local
end
Therefore, when you inherit MiniTest :: Unit :: TestCase in your test class with minimal margin, the current one is defined as a method and is guaranteed to return a value when the above MiniTest :: Spec.current call is made. This is why it works in the ruby ββstock 2.1.2 minitest.
In minitest 5.4.0
In 5.4.0 Minitest :: Spec inherits from Minitest :: Test, which does not define current (and neither of its parents). The current method is defined directly in Minitest :: Spec. It just returns Thread.current [: current_spec]:
# line 83 of ~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/lib/minitest/spec.rb
def self.current # :nodoc:
Thread.current[:current_spec]
end
The Thread.current [: current_spec] value is set in the Minitest :: Spec constructor on line 87 of the same file:
def initialize name # :nodoc:
super
Thread.current[:current_spec] = self
end
The problem is that when your test class inherits from Minitest :: Test, the Minitest :: Spec constructor is never called, and Thread.current [: current_spec] is never initialized. This means that the call to infect_an_assertion in Minitest :: Spec.current returns nil, which results in the NoMethodError you see when it tries to call assert_operator on nil. The solution is to make your test class inherit from Minitest :: Spec so that the constructor is called and Thread.current [: current_spec] gets the value.
Here's a slightly modified version of the source that works with minitest 5.4.0:
#!/usr/bin/env ruby
gem 'minitest'
require "minitest/autorun"
class TestUserKey < Minitest::Spec
def test_simple
data = 0
assert( data >= 0 )
data.must_be :>=,0
end
end
Hope this helps!
source to share
From the minitest readme it seems that the style syntax must
is part of the spec syntax, which will require you to use methods like describe
and it
instead of defining your own unit test methods.
source to share