This might be an easy question but I was unfortunately not able to find the answer on Google.
Context:
I am working on a project of my own, and I am externalizing some code in a gem (FrenchTaxSystem). It is the first I create a gem and I have difficulties using it properly.
Problem:
When calling a method (like testit) defined in the main file (french_tax_system.rb) of my gem I get a "NoMethodError: undefined method `testit' for FrenchTaxSystem:Module", though I can call constants from this same file (like FISCAL_NB_PARTS_FOR_MARRIED_COUPLE) and it puzzles me.
E.g in IRB I get that when calling a method:
[
And it is the same in my Rspecs tests inside my gem
However when calling a constant I have no error:
Main file in my gem:
french_tax_system.rb
module FrenchTaxSystem
class Error < StandardError; end
# Constants
...
FISCAL_NB_PARTS_FOR_MARRIED_COUPLE = 2
...
# Methods
## Main method
def testit
"test me"
end
end
Gem file structure:
Thank you in advance for your help,
Mth0158
This should work:
module FrenchTaxSystem
def self.testit
"test me"
end
end
I'm building a Ruby gem that includes a module that's meant to monkey-patch the Hash class to add a new method. I'm following this guide to try to do it neatly: http://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/
I've placed the module in lib/core_extensions/hash/prune.rb, and the module is declared as such:
module CoreExtensions
module Hash
module Prune
##
# Removes all pairs from the Hash for which the value is nil. Destructive!
def prune!
self.reject! { |_, v| v.nil? }
end
end
end
end
And in order to make the monkey patch take effect, I'm calling this within the main gem file:
Hash.include(CoreExtensions::Hash::Prune)
But after building the gem and trying to require it in an irb console, I get the following error: NameError: uninitialized constant Gem::CoreExtensions (Gem is a placeholder name).
I made sure to include the prune.rb file in my gemspec's files array: s.files = ['lib/gem.rb', 'lib/core_extensions/hash/prune.rb'], so I'm not sure why it can't detect the file and its modules. Can anyone help me figure this out?
Thank you!
EDIT: In case it will help anyone else - I tried to require the module file using require 'lib/core_extensions/hash/prune' but received 'cannot load such file' errors. Sticking ./ in front of the path fixed it.
The piece of code below gives me a NoMethodError. I'm a little confused why it gives me an error, and why I can't find anything about nesting methods in modules. Could someone please explain why this isn't working? Can I nest "defs" in modules?
module HowToBasic
module_function
def say_id_and_say_name(id)
# nested method
def say_id(id)
p id
end
# errors here with `say_id_and_say_name':
# undefined method `say_id' for HowToBasic:Module (NoMethodError)
# from teststuff.rb:24:in `<main>'
say_id(id)
end
end
HowToBasic.say_id_and_say_name("99999")
Version:
ruby 2.3.1p112
I had a look and couldn't find anything about this:
relates to includes NoMethodError when trying to access method defined in included module
relates to classes https://bugs.ruby-lang.org/issues/11665
seems weird Access a Ruby module's method within same module
you're missing self keyword in method definition - without it say_id_and_say_name is just an instance method, thus it can't be invoked on Module.
module HowToBasic
module_function
def self.say_id(id)
p id
end
def self.say_id_and_say_name(id)
say_id(id)
end
end
HowToBasic.say_id_and_say_name("99999")
I am using test-unit-2.5.5 with ruby 1.9.3. In http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method there is a method called at_start as part of the ruby test::unit module from version 2.5.2. I tried to use it from the examples on the page like so:
class TestAOS < Test::Unit::TestCase
Test::Unit.at_start do
puts "start"
end
Test::Unit.at_exit do
puts "Exit!"
end
But when I run my test I get the following:
NoMethodError: undefined method `at_start' for Test::Unit:Module
TestAOS at unit/TestAOS.rb:8
(root) at unit/TestAOS.rb:7
Do I need to do anything first before this method can be used? I'm new to ruby
When I comment out the at_start bloack and run the test I get a different error for at_exit:
NoMethodError: private method `at_exit' called for Test::Unit:Module
TestAOS at unit/TestAOS.rb:12
(root) at unit/TestAOS.rb:7
A
In the example provided by your link the
Test::Unit.at_start do
puts "start"
end
is called outside of test class. You are calling it from inside of your test class. Just move it outside of your TestAOS
I have been using the MiniTest that comes with Ruby 2.1 without a problem. I would subclass MiniTest::Unit:TestCase create a couple methods like 'test_simple', and everything just worked. I would use Expectations and Asserts without a problem.
I have upgraded Minitest to 5.4.0 using a gem. Everywhere I use Expectations (musts and wonts) I get a strangle error. Example of the 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 passes without a problem, but 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 strange part is what is the nil:NilClass in the error can't be nil; it's 0. Even I change the Fixnum to a String, I still get the same error.
If I change the test to a spec test, everything works again. So I can't use Expectations with in Unit Tests? IF that is the case, could someone explain why?
The short answer is that with 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 the stock ruby 2.1.2 (no 5.4.0 minitest gem installed, slightly cleaned up 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 error listed above, install minitest 5.4.0:
gem install minitest -v 5.4.0
Now the code fails with "NoMethodError: undefined method `assert_operator' for nil:NilClass". You now have both versions of minitest installed:
~/.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 dig into what exactly is happening. The expectations are defined with a call to infect_an_assertion, like this:
infect_an_assertion :assert_operator, :must_be, :reverse
For 5.4.0 that call happens in ~/.rvm/gems/ruby-2.1.2/gems/minitest-5.4.0/lib/minitest/expectations.rb. It's roughly the same in both versions, it just happens in a different place.
infect_an_assertion is also roughly the same for both versions. For :must_be it winds up making this call, which is identical between the two versions of minitest:
MiniTest::Spec.current.#{meth}(args.first, self, *args[1..-1])
They're doing some metaprogramming here, the call at runtime 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 on nil.
In the stock minitest from ruby 2.1.2:
Here MiniTest::Spec inherits from MiniTest::Unit::TestCase. TestCase defines the method current and returns a value that was set in the initialize method. You can see this all happening around line 1303 of ~/.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 from MiniTest::Unit::TestCase in your test class with the stock minitest, current is defined as a method and is guaranteed to return a value when the above call to MiniTest::Spec.current is made. This is why it works in the stock ruby 2.1.2 minitest.
In minitest 5.4.0
In 5.4.0, Minitest::Spec inherits from Minitest::Test, which does not define current (nor do any of its parents). The current method is defined directly on Minitest::Spec. It simply 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 value of Thread.current[:current_spec] 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 never gets called and Thread.current[:current_spec] is never initialized. That means the call in infect_an_assertion to Minitest::Spec.current returns nil, which results in the NoMethodError you're seeing 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 a value.
Here is a slightly modified version of the original code 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!
From the minitest readme, it seems like the must style syntax is a part of the spec syntax, which would require you to use methods like describe and it instead of defining your own unit test methods.