Hello,
while I was debugging some randomly failed tests in discovery, I stumbled upon this code:
class SomeTest < ActionController::TestCase
include FactImporterIsolation
allow_transactions_for_any_importer
# ...
end
where allow_transactions_for_any_importer
stubs a global object (FactImporter.any_instance
actually). Now, I am not a Ruby expert, but Mocha is supposed to clean all stubbed methods after test is finished according to docs and I confirmed this in its codebase, but when this is done outside of test block, it won’t happen - the object stays stubbed forever. Here is a fully working example:
require "mocha"
require "singleton"
require 'test/unit'
require 'mocha/test_unit'
class ASingleton
include Singleton
def test
"real"
end
end
module AStubber
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def stub_global_object
ASingleton.any_instance.stubs(:test).returns("stubbed")
end
end
end
class TestOne < Test::Unit::TestCase
def test_real
# will fail because ASingleton is already stubbed
assert_equal "real", ASingleton.instance.test
end
def test_stub
ASingleton.any_instance.stubs(:test).returns("stubbed")
assert_equal "stubbed", ASingleton.instance.test
end
end
class TestTwo < Test::Unit::TestCase
include AStubber
stub_global_object
def test_real
assert_equal "real", ASingleton.instance.test
end
def test_stub
ASingleton.any_instance.stubs(:test).returns("stubbed")
assert_equal "stubbed", ASingleton.instance.test
end
end
This will always fail because global objects are not destroyed:
MOCHA_OPTIONS=debug ruby mocha_test.rb -v
Since Rails does lazy loading, if this is also active during testing, this could be reason of some random failures.
This particular case needs to be fixed, now I wonder if we are able to detect such stubs at runtime and error out early to detect other possible probems and avoid regressions. If you know there is such a feature in Mocha, let’s try that. If there is none, we need one: