Module: Protobuf::RSpec::Helpers::InstanceMethods

Defined in:
lib/protobuf/rspec/helpers.rb

Instance Method Summary collapse

Instance Method Details

#local_rpc(rpc_method, request) {|service| ... } ⇒ Protobuf::Message or String

Call a local RPC service to test responses and behavior based on the given request (without testing the underlying socket implementation).

Examples:

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

Yields:

  • (service)


122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 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

#mock_rpc(klass, method, callbacks = {}) ⇒ Mock Also known as: mock_service, mock_remote_service

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.

Examples:

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

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

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

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


294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# 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

#request_class(endpoint) ⇒ Object

Returns the request class for a given endpoint of the described class

Examples:

# With a create endpoint that takes a UserRequest object:
request_class(:create) # => UserRequest


328
329
330
# File 'lib/protobuf/rspec/helpers.rb', line 328

def request_class(endpoint)
  subject_service.rpcs[endpoint].request_type
end

#response_class(endpoint) ⇒ Object

Returns the response class for a given endpoint of the described class

Examples:

# With a create endpoint that takes a UserResponse object:
response_class(:create) # => UserResponse


338
339
340
# File 'lib/protobuf/rspec/helpers.rb', line 338

def response_class(endpoint)
  subject_service.rpcs[endpoint].response_type
end

#rpc(rpc_method, request) ⇒ Protobuf::Message or Protobuf::Rpc::PbError

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.

Examples:

Test an RPC method


it "returns a user" do
  response = rpc(:find, request)
  response.should eq user
end


152
153
154
155
156
157
158
159
# 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

#rpc_env(rpc_method, request) ⇒ Protobuf::Rpc::Env Also known as: env_for_request

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`.

Examples:

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


183
184
185
186
187
188
189
190
191
192
193
194
195
196
# 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

#subject_serviceObject



50
51
52
# File 'lib/protobuf/rspec/helpers.rb', line 50

def subject_service
  self.class.subject_service
end

#wrapped_request(rpc_method, request) ⇒ Protobuf::Socketrpc::Request

Returns the request wrapper that is encoded and sent over the wire when calling an RPC method with the given request



349
350
351
352
353
354
355
356
357
358
# 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