Reputation: 57
I want to monkey patch a String#select method, and want to create a test suite that checks that it doesn't use Array#select.
I tried creating a bunch of tests using to_not receive
, using both to_not receive(Array:select)
and just to_not receive(:select)
. I also tried using an array (string.chars) instead of the string . Google and stack overflow did not bring an answer.
describe "String#select" do
it "should not use built-in Array#select" do
string = "HELLOworld".chars
expect(string).to_not receive(Array:select)
end
end
Expected: a working test suite that checks that Array#method has not been used in the whole method.
Actual output: I'm getting an error that not enough arguments have been used. Output log below:
1) RECAP EXERCISE 3 Proc Problems: String#select should not use built-in Array#select
Failure/Error: expect(string).to_not receive(Array:select)
ArgumentError:
wrong number of arguments (given 0, expected 1..4)
# ./spec/problems_spec.rb:166:in `select'
# ./spec/problems_spec.rb:166:in `block (4 levels) in <top (required)>'
Upvotes: 1
Views: 988
Reputation: 6649
First of all: tests are supposed to check the results of methods called, not the way they are implemented. Relying to much on this would get you in trouble.
But there might be a legit reasons to do it, but think hard of you can test it other way:
Let's say that String#select
uses Array#select
internally, and the latter is buggy under some circumstances. It's better to make a test, setting up the universe in a way that would trigger the bug and check that the buggy behavior is not present . Then patch the String#select
and have the test green. It's much better approach, because the test now tells everyone why you're not supposed to use Array#select
internally. And if the bug is removed, it's the easiest thing under the sun to remove patch and check if the spec is still green.
That being said, if you still need that you can use expect_any_instance_of
to acomplish that, for example this spec would fail:
class String
def select(&block)
split.select(&block) # remove this to make the spec pass
end
end
specify do
expect_any_instance_of(Array).not_to receive(:select)
'foo'.select
end
If you don't want to use expect_any_instance_of
(because reasons), you can temporarily overwrite a method in a class to fail:
class String
def select(&block)
#split.select(&block)
end
end
before do
class Array
alias :backup_select :select
def select(*)
raise 'No'
end
end
end
after do
class Array
alias :select :backup_select # bring the original implementation back
end
end
specify do
expect { 'foo'.select }.not_to raise_error
end
Aliasing is needed to bring back the original implementation, so you don't mess up the specs that are run after this one.
But you can see how involved and messy this approach is.
Anyway - what you're trying to achieve is most probably a design issue, but it's hard to tell without more details.
Upvotes: 2