I'm trying to stub:
Thing.where(uuid: options['uuid']).first
Via:
allow(Thing).to receive_message_chain(:where, :first)
.with(uuid: thing_double.uuid)
.and_return(nil)
But this is returning:
#<Double (anonymous)> received :first with unexpected arguments
expected: ({:uuid=>123})
got: (no args)
Is there a different way I should be validating arguments for message chains?
It doesn't appear that you can use with in combination with receive_message_chain when the arguments pertain anything other than the final method. Thus the message:
#<Double (anonymous)> received :first with unexpected arguments
This makes sense -- how can RSpec know which method in the chain should receive the arguments?
To verify the argument expectation, don't stub the chain, just stub where
allow(Thing).to receive(:where).with(uuid: 1).and_return([])
expect(Thing.where(uuid: 1).first).to eq nil
Or omit the arguments:
allow(Thing).to receive_message_chain(:where, :first).and_return(nil)
expect(Thing.where(uuid: 1).first).to eq nil
receive_message_chain is not recommended IMO. From the docs:
you should consider any use of receive_message_chain a code smell
Here's how we addresses a similar situation:
expect(Theme).to receive(:scoped).and_return(double('scope').tap do |scope|
expect(scope).to receive(:includes).with(:categories).and_return scope
expect(scope).to receive(:where).with(categories: { parent_id: '1'}).and_return scope
expect(scope).to receive(:order).with('themes.updated_at DESC').and_return scope
expect(scope).to receive(:offset).with(10).and_return scope
expect(scope).to receive(:limit).with(10000)
end)
In recent months, by pure accident, I discovered that you can actually chain your "with" invocation in the order of the message chain. Thus, the previous example, becomes this:
expect(Theme).to receive_message_chain(:scoped, :includes, :where, :order, :offset, :limit).with(no_args).with(:categories).with(categories: { parent_id: '1'}).with('themes.updated_at DESC').with(10).with(10000)
It's sometimes error prone (I'll intermittently get an error saying "wrong number of arguments (0 for 1+)"; although this seems to only happen when performing multiple receive_message_chains in a single test), but you can also opt for chaining your "with" methods thus:
expect(User).to receive_message_chain(:where, :order).with(active: true).with('name ASC')
Related
I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example
With Minitest Spec in Rails I'm trying to check if an ActiveSupport::TimeWithZone is in a certain range. I thought to use the between? method that takes the min and max of the range. Here's how I'm expressing that in Minitest Spec:
_(language_edit.curation_date).must_be :between?, 10.seconds.ago, Time.zone.now
but it gives me this error:
Minitest::UnexpectedError: ArgumentError: wrong number of arguments (1
for 2)
What am I doing wrong?
Looks like must_be is implemented as infect_an_assertion :assert_operator, :must_be
assert_operator
# File lib/minitest/unit.rb, line 299
def assert_operator o1, op, o2, msg = nil
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
assert o1.__send__(op, o2), msg
end
What if you use assert directly?
Example:
class DateTest < ActiveSupport::TestCase
test "using assert with between? should work" do
a = 5.seconds.ago
assert a.between?(10.seconds.ago, Time.zone.now)
end
end
Thanks to radubogdan for showing me some of the code behind the must_be method. It looks like it's designed to be used with operators like this:
_(language_edit.curation_date).must_be :>, 10.seconds.ago
and a side-effect of this is it works with boolean methods that take one or no arguments, but not with methods that take more than one argument. I think I'm supposed to do this:
_(language_edit.curation_date.between?(10.seconds.ago, Time.zone.now)).must_equal true
Using rspec, I would like to write an expectation that the following:
#clients = Client.find_all_by_status('active').sort_by {|client|client['name'].downcase}
Is called correctly.
#client = stub_client({ :name => 'abc' })
Client.should_receive(:find_all_by_status).with('active').and_return([#client])
This works but doesn't test that the sort_by clause is called.
Is there a syntax that will verify that the calls to find_all_by_status is followed by the sort_by)?
The easiest way to do this is to write two expectations:
Client.should_receive(:find_all_by_status).with('active').and_return([#client])
Client.should_receive(:sort_by)
Also I second the earlier comment about using order instead of sort_by for performance reasons.
I am testing validation of my models with rspec and am expecting an error message. However, the exact text of the message is likely to change, so I want to be a bit more forgiving and only check for a partial message.
Since the Spec::Matchers::include method only works for strings and collections, I'm currently using this construct:
#user.errors[:password].any?{|m|m.match(/is too short/)}.should be_true
This works but seems a bit cumbersome to me. Is there a better (i.e., faster or more ruby-like) way to check an array for the inclusion of a string by regex, or perhaps an rspec matcher that does just this?
I would recommend doing
#user.errors[:password].to_s.should =~ /is too short/
Simply because it will give you a more helpful error when it fails. If you use be_any then you get a message like this...
Failure/Error: #user.errors[:password].should be_any{ |m| m =~ /is too short/}
expected any? to return true, got false
However, if you use the to_s method then you will get something like this:
Failure/Error: #user.errors[:password].to_s.should =~ /is too short/
expected: /is to short/
got: "[]" (using =~)
Diff:
## -1,2 +1,2 ##
-/is too short/
+"[]"
So you can see the reason for the failure and don't have to go digging much to figure out why it is failing.
Using RSpec 3 expect syntax with matchers composing:
To match all:
expect(#user.errors[:password]).to all(match /some message/)
To match any:
expect(#user.errors[:password]).to include(match /some message/)
expect(#user.errors[:password]).to include a_string_matching /some message/
You can put the following code in spec/support/custom_matchers.rb
RSpec::Matchers.define :include_regex do |regex|
match do |actual|
actual.find { |str| str =~ regex }
end
end
Now you can use it like this:
#user.errors.should include_regex(/is_too_short/)
and be sure you have something like this in spec/spec_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
I don't think it makes a performance difference, but a more RSpec-like solution would be
#user.errors[:password].should be_any { |m| m =~ /is too short/ }
Both above answer are good. I would, however, use the newer Rspec expect syntax
#user.errors[:password].to_s.should =~ /is too short/
becomes
expect(#user.errors[:password].to_s).to match(/is too short/)
More great info here: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
My solution to this is similar to #muirbot's. I use a custom matcher. However, I use the real include matcher, but augment it with the custom matcher as an argument. Load this somewhere before your suite runs (e.g. in spec/support/matchers.rb, in turn loaded by spec/spec_helper.rb):
RSpec::Matchers.define(:a_string_matching) do |expected|
match do |actual|
actual =~ expected
end
end
Then your expectation can be written like this:
expect(#user.errors[:password]).to include(a_string_matching(/is too short/))
Just another option
#user.errors[:password].grep /is too short/
My controller is able to create a child book_loan. I am trying to test this behavior in a functional test but am having a hard time using the assert_difference method. I've tried a number of ways of passing the count of book_loans to assert_difference with no luck.
test "should create loan" do
#request.env['HTTP_REFERER'] = 'http://test.com/sessions/new'
assert_difference(books(:ruby_book).book_loans.count, 1) do
post :loan, {:id => books(:ruby_book).to_param,
:book_loan => {:person_id => 1,
:book_id =>
books(:dreaming_book).id}}
end
end
can't convert BookLoan into String
assert_difference(books(:ruby_book).book_loans,:count, 1)
NoMethodError: undefined method 'book_loans' for #
assert_difference('Book.book_loans.count', +1)
can't convert Proc into String
assert_difference( lambda{books(:ruby_book).book_loans.count}, :call, 1 )
It looks like assert_difference expects a string, which it will eval before and after the block. So the following may work for you:
assert_difference('books(:ruby_book).book_loans.count', 1) do
...
end
I was having trouble with this too and just figured out how this works. Like the original post, I too was trying something like this:
# NOTE: this is WRONG, see below for the right way.
assert_difference(account.users.count, +1) do
invite.accept(another_user)
end
This doesn't work because there is no way for assert_difference to perform an action before it runs the block and after it runs the block.
The reason the string works is that the string can be evaluated to determine if the expected difference resulted.
But a string is a string, not code. I believe a better approach is to pass something that can be called. Wrapping the expression in a lambda does just that; it allows assert_difference to call the lambda to verify the difference:
assert_difference(lambda { account.users.count }, +1) do
invite.accept(another_user)
end