module Protobuf::RSpec::Helpers::InstanceMethods
Public Instance Methods
Call a local RPC service to test responses and behavior based on the given request (without testing the underlying socket implementation).
@example Test a local service method
# Implementation module Services class UserService < Protobuf::Rpc::Service def create if request.name user = User.create_from_proto(request) respond_with(user) else rpc_failed 'Error: name required' end end def notify user = User.find_by_guid(request.guid) if user Resque.enqueue(EmailUserJob, user.id) respond_with(:queued => true) else rpc_failed 'Error: user not found' end end end end # Spec describe Services::UserService do describe '#create' do subject { local_rpc(:create, request) } context 'when request is valid' do let(:request) { { :name => 'Jack' } } let(:user_mock) { FactoryGirl.build(:user) } before { User.should_receive(:create_from_proto).and_return(user_mock) } it { should eq(user_mock) } end context 'when name is not given' do let(:request) { :name => '' } it { should =~ /Error/ } end end describe '#notify' do let(:request) { { :guid => 'USR-123' } } let(:user_mock) { FactoryGirl.build(:user) } subject { local_rpc(:notify, request) } context 'when user is found' do before { User.should_receive(:find_by_guid).with(request.guid).and_return(user_mock) } before { Resqueue.should_receive(:enqueue).with(EmailUserJob, request.guid) its(:queued) { should be_true } end context 'when user is not found' do before { Resque.should_not_receive(:enqueue) } it { should =~ /Error/ } end end end
@param [Symbol, String] method a symbol or string denoting the method to call. @param [Protobuf::Message or Hash] request the request message of the expected type for the given method. @return [Protobuf::Message or String] the resulting protobuf message or error string
# File lib/protobuf/rspec/helpers.rb, line 122 def local_rpc(rpc_method, request) env = rpc_env(rpc_method, request) service = subject_service.new(env) yield(service) if block_given? # Dispatch the RPC method invoking all of the filters if service.respond_to?(:callable_rpc_method) service.callable_rpc_method(rpc_method).call else service.call(rpc_method) end service.response end
Create a mock service that responds in the way you are expecting to aid in testing client -> service calls. In order to test your success callback you should provide a :response object. Similarly, to test your failure callback you should provide an :error object.
Asserting the request object can be done one of two ways: direct or explicit. If you would like to directly test the object that is given as a request you should provide a :request object as part of the cb_mocks third parameter hash. Alternatively you can do an explicit assertion by providing a block to mock_rpc. The block will be yielded with the request object as its only parameter. This allows you to perform your own assertions on the request object (e.g. only check a few of the fields in the request). Also note that if a :request param is given in the third param, the block will be ignored.
@example Testing the client on_success callback
# Method under test def create_user(request) status = 'unknown' Proto::UserService.client.create(request) do |c| c.on_success do |response| status = response.status end end status end ... # spec it 'verifies the on_success method behaves correctly' do response_mock = mock('response_mock', :status => 'success') mock_rpc(Proto::UserService, :client, :response => response_mock) create_user(request).should eq('success') end
@example Testing the client on_failure callback
# Method under test def create_user(request) status = nil Proto::UserService.client.create(request) do |c| c.on_failure do |error| status = 'error' ErrorReporter.report(error.message) end end status end ... # spec it 'verifies the on_success method behaves correctly' do error_mock = mock('error_mock', :message => 'this is an error message') mock_rpc(Proto::UserService, :client, :error => error_mock) ErrorReporter.should_receive(:report).with(error_mock.message) create_user(request).should eq('error') end
@example Testing the given client request object (direct assert)
# Method under test def create_user request = ... # some operation to build a request on state Proto::UserService.client.create(request) do |c| ... end end ... # spec it 'verifies the request is built correctly' do expected_request = ... # some expectation mock_rpc(Proto::UserService, :client, :request => expected_request) create_user(request) end
@example Testing the given client request object (block assert)
# Method under test def create_user request = ... # some operation to build a request on state Proto::UserService.client.create(request) do |c| ... end end ... # spec it 'verifies the request is built correctly' do mock_rpc(Proto::UserService, :client) do |given_request| given_request.field1.should eq 'rainbows' given_request.field2.should eq 'ponies' end create_user(request) end
@param [Class] klass the service class constant @param [Symbol, String] method a symbol or string denoting the method to call @param [Hash] callbacks provides expectation objects to invoke on_success (with :response), on_failure (with :error), and the request object (:request) @param [Block] optional. When given, will be invoked with the request message sent to the client method @return [Mock] the stubbed out client mock
# File lib/protobuf/rspec/helpers.rb, line 294 def mock_rpc(klass, method, callbacks = {}) client = double('Client', :on_success => true, :on_failure => true) allow(client).to receive(method).and_yield(client) allow(klass).to receive(:client).and_return(client) case when callbacks[:request] then expect(client).to receive(method).with(callbacks[:request]) when block_given? then expect(client).to receive(method) do |given_req| yield(given_req) end else expect(client).to receive(method) end success = callbacks[:success] || callbacks[:response] allow(client).to receive(:on_success).and_yield(success) unless success.nil? failure = callbacks[:failure] || callbacks[:error] allow(client).to receive(:on_failure).and_yield(failure) unless failure.nil? client end
Returns the request class for a given endpoint of the described class
@example
# With a create endpoint that takes a UserRequest object: request_class(:create) # => UserRequest
# File lib/protobuf/rspec/helpers.rb, line 328 def request_class(endpoint) subject_service.rpcs[endpoint].request_type end
Returns the response class for a given endpoint of the described class
@example
# With a create endpoint that takes a UserResponse object: response_class(:create) # => UserResponse
# File lib/protobuf/rspec/helpers.rb, line 338 def response_class(endpoint) subject_service.rpcs[endpoint].response_type end
Make an RPC call invoking the entire middleware stack (without testing the underlying socket implementation). Works the same as `local_rpc`, but invokes the entire RPC middleware stack.
@example Test an RPC method
it "returns a user" do response = rpc(:find, request) response.should eq user end
@param [Symbol, String] method a symbol or string denoting the method to call. @param [Protobuf::Message or Hash] request the request message of the expected type for the given method. @return [Protobuf::Message or Protobuf::Rpc::PbError] the resulting Protobuf
message or RPC error.
# File lib/protobuf/rspec/helpers.rb, line 152 def rpc(rpc_method, request) request_wrapper = wrapped_request(rpc_method, request) env = ::Protobuf::Rpc::Env.new('encoded_request' => request_wrapper.encode) env = ::Protobuf::Rpc.middleware.call(env) env.response end
Initialize a new RPC env object simulating what happens in the middleware stack. Useful for testing a service class directly without using `rpc` or `local_rpc`.
@example Test an RPC method on the service directly
describe "#create" do # Initialize request and response # ... let(:env) { rpc_env(:create, request) } subject { described_class.new(env) } it "creates a user" do subject.create subject.response.should eq response end end
@param [Symbol, String] method a symbol or string denoting the method to call. @param [Protobuf::Message or Hash] request the request message of the expected type for the given method. @return [Protobuf::Rpc::Env] the environment derived from an RPC request.
# File lib/protobuf/rspec/helpers.rb, line 183 def rpc_env(rpc_method, request) request = request_class(rpc_method).new(request) if request.is_a?(Hash) ::Protobuf::Rpc::Env.new( 'caller' => 'protobuf-rspec', 'service_name' => subject_service.to_s, 'method_name' => rpc_method.to_s, 'request' => request, 'request_type' => request_class(rpc_method), 'response_type' => response_class(rpc_method), 'rpc_method' => subject_service.rpcs[rpc_method], 'rpc_service' => subject_service ) end
# File lib/protobuf/rspec/helpers.rb, line 50 def subject_service self.class.subject_service end
Returns the request wrapper that is encoded and sent over the wire when calling an RPC method with the given request
@param [Symbol, String] method a symbol or string denoting the method to call. @param [Protobuf::Message or Hash] request the request message of the expected type for the given method. @return [Protobuf::Socketrpc::Request] the wrapper used to transmit RPC requests.
# File lib/protobuf/rspec/helpers.rb, line 349 def wrapped_request(rpc_method, request) request = request_class(rpc_method).new(request) if request.is_a?(Hash) ::Protobuf::Socketrpc::Request.new( :service_name => subject_service.to_s, :method_name => rpc_method.to_s, :request_proto => request.encode, :caller => 'protobuf-rspec' ) end