-
-
Notifications
You must be signed in to change notification settings - Fork 159
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
deprecate current method dispatch behavior(jmock1) #448
base: main
Are you sure you want to change the base?
Conversation
Display deprecation warning when adding an expectation if the expectation list already contains any expectations. The intent is to change method dispatch behavior to that of jMock v2 as suggested in freerange#173 in a major version release.
I notice that this deprecation warning will necessarily be raised quite pervasively in many project test suites. Thinking about it a bit more, I'm not sure whether it makes sense to deprecate the old method dispatch behaviour, at least not until the new behaviour is available. Realistically, I can imagine there will be a lot of projects around that won't want to re-write all their tests just to upgrade to the latest version of Mocha. So I'm not sure we'd ever want to completely remove the old behaviour. I think we have a couple of options: Option A
Option B
Do you have any thoughts on which approach we should take? |
Also stepping back a bit, I think the original issue was opened in relation to questions I was getting like the one in #171 back in 2013. I don't think I've had many/any questions like that since around that time, so I wonder whether it's really worthwhile making this change. @nitishr What do you think...? It might be worth looking at the jMock release notes to understand their rationale for making the change. |
I couldn't find jMock release notes, other than this:
But I found this in the current readme here
That's also the main (probably the sole) reason I think the change in method dispatch order is worth making. The rationale behind the old jmock and current mocha dispatch order seems to be:
However, I don't think that rationale is strong enough to justify providing what appears to be a conterintuitive dispatch order to me. I can't imagine someone who hasn't read the documentation not being surprised by that behavior, even if they might understand and make peace with after reading the documentation. I suspect that's the reason jMock adopted the opposite and (to me) more intuitive order. I could try asking @sf105 or @npryce whether they might remember and be willing to share the reasoning, as well as the impact of completely switching off the old behavior with a major version release, since that's one of our concerns. Thoughts? |
@nitishr Thanks for researching the jMock documentation. I'm continuing to think about this and ask colleagues what behaviour they find least surprising. To get another data point, I've just started investigating how RSpec mocks work, but I've run out of time. So far I've created an RSpec mocks version of this acceptance test: RSpec.describe 'rspec-mocks method dispatch' do
it 'finds latest matching expectation' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
end
it 'finds latest expectation which has not stopped matching' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).once.and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(1)
end
it 'keeps finding later stub and so never satisfies earlier expectation (expected to fail)' do
mock = instance_double('mock')
expect(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
end
it 'finds later expectation until it stops matching then find earlier stub' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
expect(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(1)
end
it 'finds latest expectation with range of expected invocation count which has not stopped matching' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).at_least(2).at_most(3).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(1)
end
end And I see the following output:
I think it would be good to think about this a bit more and ideally have a stronger motivation/explanation for making any changes. |
I've updated the rspec example as below: RSpec.describe 'rspec-mocks method dispatch' do
it 'finds latest matching allowance' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
end
it 'does not find earlier allowance even after a constrained later allowance is used up' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).once.and_return(2)
expect(mock.method).to eq(2)
expect { mock.method }.to raise_error(RSpec::Mocks::MockExpectationError)
end
it 'finds prior expectation before later allowance' do
mock = instance_double('mock')
expect(mock).to receive(:method).and_return(1)
allow(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
end
it 'finds later expectation before prior allowance' do
mock = instance_double('mock')
allow(mock).to receive(:method).and_return(1)
expect(mock).to receive(:method).and_return(2)
expect(mock.method).to eq(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(1)
end
it 'finds prior expectation before later expecation' do
mock = instance_double('mock')
expect(mock).to receive(:method).and_return(1)
expect(mock).to receive(:method).at_least(:once).and_return(2)
expect(mock.method).to eq(1)
expect(mock.method).to eq(2)
expect(mock.method).to eq(2)
end
end And I see the following output:
(Note: while reviewing the tests, it'll help to remember that in rspec, when no invocation counts are specified, stubs default to any number of times, while mocks default to exactly once.) So, it seems the rules are:
They seem unnecessarily complicated to me. Not sure if they've been deliberaty designed or accidentally implemented. |
Thanks so much for investigating RSpec's behaviour so thoroughly - that's really helpful and interesting! I'm afraid I still haven't had time to think about this in any detail. Are you still thinking that reversing the current Mocha matching order (like you implemented in #395) is the "right" solution. Have you had a chance to ask any other Ruby-ists what they think? Given it's a significant change, it would be good to canvas as much opinion as possible to make the behaviour as unsurprising as possible. |
Display deprecation warning when adding an expectation if the expectation list
already contains any expectations. The intent is to change method dispatch
behavior to that of jMock v2 as suggested in #173 in a major version release.