New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Specs with Mock started failing on v5.16.0 #912
Comments
Likely due to changes in argument handling in Seeing: But I am calling it with one argument and one keyword argument. |
How can we allow any In 5.15 it was possible with mock.expect(:my_method, 42, [Hash]) |
With this change kwargs should not be in the args array anymore: |
CI runs bundle update so minitest got updated and my PR started failing due to this change minitest/minitest@6e06ac9 I will relax the minitest requirement once the failing specs can be fixed, but I don't want that to block this. It wasn't immediately obvious to me what needs to change in our tests to get them passing. There is also an open issue here minitest/minitest#912 regarding this change.
We also had a broken test when updating from 5.15.0 to 5.16.0. We use MiniTest::Mock through Rails ActiveSupport |
@sedubois |
I didn't realize that by adding real kwsupport I'd break people's kludged keywords... how were you doing it? I assume a hash on the end of args? Can someone provide some failing examples? This issue is not workable as-is. |
@eileencodes lemme know if there's anything I can do to help you on your end. |
I ran into this one as well. In my case it all related to testing
I'll try to distil a minimal example. I had builds for Ruby 2.5, 2.7 and 3.1 - it only failed on 2.7. |
Got a minimal repro. In my case the issue is that I have some objects that implement require 'minitest/autorun'
require 'observer'
class Example
include Observable
def trigger
changed
notify_observers(:triggered, self)
end
# Remove this method and the test will pass.
# In Ruby 2.7 this cause the mock-observer interaction to fail with MiniTest 5.16.0.
def to_hash
{ foo: 'bar' }
end
end
class ExampleTest < Minitest::Test
def test_example_trigger
observable = Example.new
mock = Minitest::Mock.new
mock.expect(:hash, mock.object_id)
mock.expect(:event, nil, [:triggered, observable])
observable.add_observer(mock, :event)
observable.trigger
end
end Ruby 2.7 and MiniTest 5.16.0:
In Ruby 2.5 and Ruby 3.1 that example works just fine. If I remove the Previous versions of MiniTest also worked fine with the above example. Looks like something in Ruby 2.7 and MiniTest 5.16.0 causes the |
Just experienced it as well. Ruby 3.1.2, Rails 6.1.6 and MiniTest 5.16.0. 5.15.0 works fine.
It's caused by:
Called in the code as:
It's an old code base, those things were not touched for a long time. |
Think you need something like |
We also just ran into this for some of Google's libraries. In 5.15, when mocking a method with keyword arguments, we'd need to pass the keyword arguments as a hash at the end of the positional arguments array passed to |
CI runs bundle update so minitest got updated and my PR started failing due to this change minitest/minitest@6e06ac9 I will relax the minitest requirement once the failing specs can be fixed, but I don't want that to block this. It wasn't immediately obvious to me what needs to change in our tests to get them passing. There is also an open issue here minitest/minitest#912 regarding this change.
@baelter unfortunately, you have no idea what the real method takes at the point where And I'm in the sad position of looking at the last arg and not being sure if the user really wanted to pass a hash or if they wanted kwargs... Maybe a warning for a while? |
What would the warning be? Would behaviour remain as in 5.16? (How would you then mock a method call where the only or last item is a Hash?) |
I now have a fix for the old-kwargs scenario as shown by @zrzka ... I don't yet have a fix for 2.6/2.7 for the observer issue from @thomthom yet (but I haven't investigated, so I'm not sure it is out of my hands or not). Current fix: Set |
OK. I've investigated the observer scenario. Because Observer in 2.7 uses The workarounds are to either upgrade past ruby 2.7, not upgrade into minitest 5.16, or not declare your class as "is-a Hash" by having a If it works, but warns, it's still working. |
I misstated the observer scenario above. With the code I just pushed, here's the outcomes: # without ENV:
mock.expect(:event, nil, [:triggered, observable]) # original: fails
mock.expect(:event, nil, [:triggered, **observable]) # use to_hash: fails
mock.expect(:event, nil, [:triggered], observable) # kwargs: pass
mock.expect(:event, nil, [:triggered], **observable) # kwargs/to_hash: pass
# with ENV MT_KWARGS_HACK=1
mock.expect(:event, nil, [:triggered, observable]) # original: fails
mock.expect(:event, nil, [:triggered, **observable]) # use to_hash: pass
mock.expect(:event, nil, [:triggered], observable) # kwargs: pass
mock.expect(:event, nil, [:triggered], **observable) # kwargs/to_hash: pass I doubt I'm going to come up with a working version that deals with: ruby 2.7's observer + Example w/ to_hash + minitest 5.16 with or without the ENV adjustment above. I think it's just gonna have to be modified in 1 of the 3 ways above. Given that the problem goes away with either: not treating your class as a is-a-Hash or using ruby 3.0+ or using your expectations in a more complaint way... ... well let's just say I'm open to a compelling argument for more compatibility, I just doubt it'll be that compelling at this point. |
I have a rule about not releasing after midnight ESPECIALLY if liquor has been involved... but this fix will come out soon enough. |
Thanks for investigating that. I might not really need to use |
to_hash is like to_ary and friends (vs to_h or to_a)... Only should be used if they're really implicitly "is-a"... So def worth considering |
OK. This is released in 5.16.1. Please update, (and please only use this workaround in order to have working tests while you transition to proper kwargs calling conventions.) |
One type of case I wasn't able to translate is: mock.expect(return_value, [positional_1, Hash]) As to allow any keyword arg. It's not necessarily good code, but the translation is really non-trivial. |
@casperisfine and this doesn't work using the env? Can you scratch me up a quick test? You can steal a skeleton from 2-3 commits ago. |
I haven't tried, I just rewrote these mocks using
👀 |
Here you go: Shopify@a527a74, apparently it doesn't work with the env either. |
I have another example of code that breaks with teh upgrade in Ruby 2.7 (works fine in 3.0+) and doesn't work with the ENV var hack. My PR is here rails/rails#45370, failures here https://buildkite.com/rails/rails/builds/87503#01818819-5daf-4cd7-bdfb-e48ec43c837c and you can run one of the Railties tests with |
- minitest 5.16 broke the build - minitest/minitest#912 Signed-off-by: Peter Boling <peter.boling@gmail.com>
This comment was marked as off-topic.
This comment was marked as off-topic.
@casperisfine I have your test in and passing...
|
@tsugimoto |
It's failing on CI: https://github.com/Shopify/minitest/runs/6999031451?check_suite_focus=true#step:5:477 |
Hey, thanks for the update. I would like to know what are For example, tests implemented following this https://www.reinhardt.io/ruby/testing/2021/05/25/stubbing-and-mocking-in-minitest.html started to fail. test "it will set the type to load" do
mock = Minitest::Mock.new
args_checker = lambda do |args|
assert_equal :load, args[:type]
end
mock.expect :notify!, true, [args_checker]
SomeClass.stub(:new, mock) do
logic_that_triggers_notify
end
mock.verify
end
# method in question is defined like this:
def notify!(type:)
something
end The number of tests that fail is small < 5. So I can change them but would like to understand better whether the tests could be specified in a more proper way. |
@casperisfine sorry... by "in" I meant locally... I've finally polished, committed, and pushed. |
@eileencodes yeah... i'mma need some help untangling that patch into a test I can debug |
@miguelperez I'm guessing you want something more like: mock.expect :notify!, true, [], type: :load (no |
@eileencodes BUT!I don't want to release until I get a thumbs up from you (or a thumbs down and smaller repro) |
It doesn't look like it's fixed, but I don't know how to get the unreleased version hooked up to Rails CI to do a full test it since there's no gemspec. I did this locally by creating a gemspec manually but that's not going to work on Rails CI without forking, which I guess I can do to see the full set of failures. Is there another way to do this more easily? I've never used hoe so I'm not sure how to make it play nicely with bundler's expectations. |
@eileencodes I really just need a failing test extracted enough that I don't need the whole world to come with it. I think I've done it. I've got a run w/:
with the following: require "minitest/autorun"
class Redis
end
class ActiveSupport
class Cache
class RedisCacheStore
attr_accessor :kws
def initialize(**kws)
self.kws = kws
end
def redis
call(:url => "redis://localhost:6379/0",
:connect_timeout => 20,
:read_timeout => 1,
:write_timeout => 1,
:reconnect_attempts => 0,
:driver => "hiredis")
end
def call **kws
Redis.new
end
end
end
end
module ActiveSupport::Cache::RedisCacheStoreTests
REDIS_URL = "redis://localhost:6379/0"
REDIS_URLS = %w[ redis://localhost:6379/0 redis://localhost:6379/1 ]
REDIS_UP = true
DRIVER = "hiredis"
class InitializationTest < Minitest::Test
def build(**kwargs)
ActiveSupport::Cache::RedisCacheStore.new(driver: DRIVER, **kwargs.merge(pool: false)).tap(&:redis)
end
def test_stuff
default_args = {
connect_timeout: 20,
read_timeout: 1,
write_timeout: 1,
reconnect_attempts: 0,
driver: DRIVER
}
mock = Minitest::Mock.new
mock.expect(:call, Redis.new, [{ url: REDIS_URLS.first }.merge(default_args)])
mock.expect(:call, Redis.new, [{ url: REDIS_URLS.last }.merge(default_args)])
Redis.stub(:new, mock) do
@cache = build url: REDIS_URLS
assert_kind_of ::Redis, @cache.redis
end
assert_mock(mock)
end
end
end BUT! this fails across the board, regardless of version it seems. Found it here: https://buildkite.com/rails/rails/builds/87503#01818819-5d9b-439d-ba67-f905c797161c/1169-1202 can you verify that the test extraction is close enough for the repro you're seeing? It just needs to be able to run against a local copy (or install) of minitest and reproduce the error. |
I understand but I wanted to know all of the failures so I can give you an example of the different sets of failures we have rather than having to go back and forth if one set is fixed and another is not. |
@eileencodes I've run both
Unfortunately a lot of the other failing groups seem out of reach for me. I'm trying to find where one might put a global env for circleci. There might be some config on via the website that I don't have access to. It's been a few years and I think we did ours via yml files like GHA workflows... So I'm trying out putting it in |
@eileencodes PS activesupport test above runs fine w/ no extra |
I haven't heard anything back on the rails side... I'm going to release what I've got. |
This is released in 5.16.2. Thanks for reporting! @baelter, please close if this addresses your issues. Others: please open a NEW issue if you're edge cases aren't addressed. |
Works when changing to the new method signature, so that is good. We can lift the pin and update our specs. |
Hey guys, # frozen_string_literal: true
require 'minitest/autorun'
require 'minitest/spec'
class UserSychronizer
def initialize(user_id)
@user_id = user_id
end
# @param data [Hash]
def synchronize(data)
UserProfileSynchronizer.new(@user_id).synchronize(data)
end
end
class UserProfileSynchronizer
def initialize(user_id)
@user_id = user_id
end
# @param data [Hash]
def synchronize(data)
# TODO: profile sync
end
end
describe UserSychronizer do
describe '#synchronize' do
before do
@user_id = 1
@data = {
first_name: 'Foo',
last_name: 'Bar'
}
end
it 'calls UserProfileSynchronizer#synchronize' do
mock = Minitest::Mock.new
mock.expect :synchronize, nil, [@data]
UserProfileSynchronizer.stub :new, mock do
UserSychronizer.new(@user_id).synchronize(@data)
end
assert_mock mock
end
end
end It worked without any issues on
I am receiving exactly the same error if I use Any ideas? Update: |
Minitest's interface changed slightly when they added first class support for kwargs, see minitest/minitest#912 Closes #1269
# What it does Updates minitest and changes tests to pass in the newer version # Why it is important Staying on the latest version of our dependencies is good # Implementation notes Minitest's interface changed slightly when they added first class support for kwargs, see minitest/minitest#912 Closes #1269
Don't have a lot of information yet.
But some specs using
MiniTest::Mock
starting failing after upgrading minitest.Will update when I figure out what's going on.
The text was updated successfully, but these errors were encountered: