Reputation: 351
So I have this simple ruby class:
class GetRequestList
def initialize(current_user, filter_hash)
@authorizer = RequestAuthorizer.new(current_user)
@filter = RequestFilter.new(filter_hash)
end
def generate
Request.send_chain(@authorizer.method_chain)
.send_chain(@filter.method_chain)
end
end
And I want to test that Request
receives two send_chain
methods in isolation of RequestAuthorizer
and RequestFilter
implementations. To do that I'm trying to use some stubs:
require 'test_helper'
class GetRequestListTest < ActiveSupport::TestCase
test "request should be filtered by filter and role" do
Request.expects(:send_chain).twice.returns([build(:request)])
RequestFilter.stubs(:new)
RequestFilter.any_instance.stubs(:method_chain).returns([])
RequestAuthorizer.stubs(:new)
RequestAuthorizer.any_instance.stubs(:method_chain).returns([])
assert GetRequestList.new(:current_user, :filter).generate.size == 1
end
end
You see what is wrong. stubs(:new)
returns nil
and there is no instances of RequestAuthorizer
and RequestFilter
in instance variables of GetRequestList
and we get an error. I can't figure out how to stub methods on instance variables.
Any suggestions?
Upvotes: 2
Views: 5653
Reputation: 125
have you tried exposing the instance variable so you could stub it?
GetRequestList.new(:current_user, :filter).tap do |it|
def it.authorizer
@authorizer
end
install_stubs(it.authorizer)
end.generate
Upvotes: 0
Reputation: 84172
Instead of stubbing out new
to return no value, have it return something e.g.
mock_request_filter = mock()
RequestFilter.stubs(:new).returns(mock_filter)
This also enables you to get read of the stubs on any_instance
- just set them on mock_request_filter
instead.
Upvotes: 5
Reputation: 16730
This is why you should wrap instance variables inside methods. Take a look at this approach. This way your tests doesn't know about RequestFilter or RequestAuthorizer. And now you also asset that you get them as params. Notice that I also wrapped authorizer and filter initialization inside a method. You could also wrap both inside another one if your main initializer method gets more stuff on it.
class GetRequestList
def initialize(current_user, filter_hash)
initialize_authorizer
initialize_filter
end
def generate
Request.send_chain(authorizer_method_chain)
.send_chain(filter_method_chain)
end
private
def initialize_authorizer
@authorizer = RequestAuthorizer.new(current_user)
end
def initialize_filter
@filter = RequestFilter.new(filter_hash)
end
def authorizer_method_chain
@authorizer.method_chain
end
def filter_method_chain
@filter.method_chain
end
end
and the test
require 'test_helper'
class GetRequestListTest < ActiveSupport::TestCase
test "request should be filtered by filter and role" do
get_request_list = GetRequestList.new(:current_user, :filter)
get_request_list.stubs(:initialize_authorizer)
get_request_list.stubs(:initialize_filter)
get_request_list.stubs(:authorizer_method_chain).returns(:authorizer_method_chain)
get_request_list.stubs(:filter_method_chain).returns(:filter_method_chain)
Request.expects(:send_chain).with(:authorizer_method_chain).returns([build(:request)])
Request.expects(:send_chain).with(:filter_method_chain).returns([build(:request)])
assert get_request_list.generate.size == 1
end
end
I also used a symbol to replace the authorizer and filter objects because you don't even need them to be mocks. It could also be some other thing like 1
and 2
, but keeping symbols or strings lets you kinda name things properly.
Upvotes: 1