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?

+3


source to share


2 answers


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!

+2


source


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.



0


source







All Articles