protobuf-rspec 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,51 +1,128 @@
1
1
  protobuf-rspec gem
2
2
  ==================
3
3
 
4
- RSpec Helpers designed to give you mock abstraction of client or service layer. Require as protobuf/rspec/helpers and include into your running RSpec configuration.
4
+ RSpec Helpers designed to give you mock abstraction of client or service layer. Require as `protobuf/rspec` and include into your running RSpec configuration.
5
5
 
6
- **Note:** Tested to work with the [protobuf gem](https://rubygems.org/gems/protobuf) (>= 1.0).
6
+ ```ruby
7
+ # spec_helper.rb
8
+ # ...
9
+
10
+ require 'protobuf/rspec'
11
+ RSpec.configure do |config|
12
+ config.include Protobuf::RSpec::Helpers
13
+ end
14
+ ```
15
+
16
+ **Note:** Tested to work with the [protobuf gem](https://rubygems.org/gems/protobuf) (>= 2.x).
17
+
18
+ Unit-Testing Service Behavior
19
+ -----------------------------
7
20
 
8
- Mocking Client Requests (Outside-In test of the service methods)
9
- ----------------------------------------------------------------
21
+ ### `local_rpc`
10
22
 
11
- Use this method to call a local service in your application to test responses and behavior based on the given request. This should be used to outside-in test a local RPC Service without testing the underlying socket implementation or needing actual client code to invoke the method(s) under test.
23
+ To unit test your service you should use the `local_rpc` helper method. `local_rpc` helps you call the service instance method of your choosing to ensure that the correct responses are generated with the given requests. This should be used to outside-in test a local RPC Service without testing the underlying socket implementation or needing actual client code to invoke the endpoint method under test.
12
24
 
13
25
  Given the service implementation below:
14
26
 
15
27
  ```ruby
16
- module Proto
28
+ module Services
17
29
  class UserService < Protobuf::Rpc::Service
18
30
  def create
19
- user = User.create_from_proto(request)
20
- self.response = ProtoRepresenter.new(user).to_proto
31
+ if request.name
32
+ user = User.create_from_proto(request)
33
+ respond_with(user)
34
+ else
35
+ rpc_failed 'Error: name required'
36
+ end
37
+ end
38
+
39
+ def notify
40
+ user = User.find_by_guid(request.guid)
41
+ if user
42
+ Resque.enqueue(EmailUserJob, user.id)
43
+ respond_with(:queued => true)
44
+ else
45
+ rpc_failed 'Error: user not found'
46
+ end
21
47
  end
22
48
  end
23
49
  end
24
50
  ```
25
51
 
26
- This could be one way to test the implementation while ignoring the RPC backend:
27
-
52
+ Specs that test these two methods and their various cases could look something like this:
28
53
 
29
54
  ```ruby
30
- describe Proto::UserService do
55
+ describe Services::UserService do
31
56
  describe '#create' do
32
- it 'creates a new user' do
33
- create_request = Proto::UserCreate.new(...)
34
- client = call_local_service(Proto::UserService, :create, create_request)
35
- client.response.should eq(some_response_object)
57
+ subject { local_rpc(:create, request) }
58
+
59
+ context 'when request is valid' do
60
+ let(:request) { { :name => 'Jack' } }
61
+ let(:user_mock) { FactoryGirl.build(:user) }
62
+ before { User.should_receive(:create_from_proto).and_return(user_mock) }
63
+ it { should eq(user_mock) }
64
+ end
65
+
66
+ context 'when name is not given' do
67
+ let(:request) { :name => '' }
68
+ it { should =~ /Error/ }
69
+ end
70
+ end
71
+
72
+ describe '#notify' do
73
+ let(:request) { { :guid => 'USR-123' } }
74
+ let(:user_mock) { FactoryGirl.build(:user) }
75
+ subject { local_rpc(:notify, request) }
76
+
77
+ context 'when user is found' do
78
+ before { User.should_receive(:find_by_guid).with(request.guid).and_return(user_mock) }
79
+ before { Resqueue.should_receive(:enqueue).with(EmailUserJob, request.guid)
80
+ its(:queued) { should be_true }
81
+ end
82
+
83
+ context 'when user is not found' do
84
+ before { Resque.should_not_receive(:enqueue) }
85
+ it { should =~ /Error/ }
36
86
  end
37
87
  end
38
88
  end
39
89
  ```
40
90
 
91
+ ### `subject_service`
92
+
93
+ One thing to note is that `local_rpc` uses `described_class` as the class to invoke for the given method. If you need to instead test a different class than your `described_class`, simply pass a block to `subject_service` which returns the class you would like to use instead.
94
+
95
+ ```ruby
96
+ describe 'The User Service' do
97
+ subject_service { Services::UserService }
98
+
99
+ describe '#create' do
100
+ subject { local_rpc(:create, request) }
101
+ # ...
102
+ end
103
+
104
+ #...
105
+ end
106
+ ```
107
+
108
+ ### `request_class` and `response_class`
109
+
110
+ Both the `request_class` and `response_class` helper methods will return the class type for, you guessed it, the request and response type defined by the service method. This can aid in setting up the correct objects for expectations. Simply pass in the name of the endpoint you are testing to get the appropriate message class.
111
+
112
+ ```ruby
113
+ request_class(:create) # => UserCreateRequest
114
+ response_class(:create) # => User
115
+ ```
41
116
  Mocking Service Responses
42
117
  -------------------------
43
118
 
44
- 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.
119
+ 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 `:success` option. To test your failure callback you should provide a `:failure` option.
120
+
121
+
122
+ ### Testing the client `on_success` callback
45
123
 
46
- 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` hash (third parameter). Alternatively you can do an explicit assertion by providing a block to `mock_remote_service`. 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.
124
+ Passing a `:success` key as an option to `mock_rpc` will cause the `on_success` callback to be invoked with the given object. In this way you can simulate a successful service response to verify that you are handling the response appropriately. You can alternatively use the `:response` key to invoke the `on_success` block.
47
125
 
48
- ### Testing the client on_success callback
49
126
  ```ruby
50
127
  # Method under test
51
128
  def create_user(request)
@@ -58,15 +135,19 @@ Asserting the request object can be done one of two ways: direct or explicit. If
58
135
  status
59
136
  end
60
137
  ...
61
-
138
+
62
139
  # spec
63
140
  it 'verifies the on_success method behaves correctly' do
64
- mock_remote_service(Proto::UserService, :client, response: mock('response_mock', status: 'success'))
141
+ response_mock = mock('response_mock', :status => 'success')
142
+ mock_rpc(Proto::UserService, :client, :success => response_mock) # alternatively can use :response key here
65
143
  create_user(request).should eq('success')
66
144
  end
67
145
  ```
68
146
 
69
- ### Testing the client on_failure callback
147
+ ### Testing the client `on_failure` callback
148
+
149
+ Passing a `:failure` key as an option to `mock_rpc` will cause the `on_failure` callback to be invoked with the given object. In this way you can simulate a service failure and verify you are handling that failure appropriately. You can alternatively use the `:error` key to invoke the `on_failure` block.
150
+
70
151
  ```ruby
71
152
  # Method under test
72
153
  def create_user(request)
@@ -83,13 +164,17 @@ end
83
164
 
84
165
  # spec
85
166
  it 'verifies the on_success method behaves correctly' do
86
- mock_remote_service(Proto::UserService, :client, error: mock('error_mock', message: 'this is an error message'))
87
- ErrorReporter.should_receive(:report).with('this is an error message')
167
+ error_mock = mock('error_mock', :message => 'this is an error message')
168
+ mock_rpc(Proto::UserService, :client, :failure => error_mock) # alternatively can use :error key here
169
+ ErrorReporter.should_receive(:report).with(error_mock.message)
88
170
  create_user(request).should eq('error')
89
171
  end
90
172
  ```
91
173
 
92
174
  ### Testing the given client request object (direct assert)
175
+
176
+ In order to test the request object sent to the service you can pass a `:request` key whose value will be asserted with RSpec's `with` constraint paired with the `should_receive` assertion. Also note that if a `:request` option is given, the assert block will be ignored (see below).
177
+
93
178
  ```ruby
94
179
  # Method under test
95
180
  def create_user
@@ -103,12 +188,15 @@ end
103
188
  # spec
104
189
  it 'verifies the request is built correctly' do
105
190
  expected_request = ... # some expectation
106
- mock_remote_service(Proto::UserService, :client, request: expected_request)
191
+ mock_rpc(Proto::UserService, :client, :request => expected_request)
107
192
  create_user(request)
108
193
  end
109
194
  ```
110
195
 
111
- ### Testing the given client request object (explicit assert)
196
+ ### Testing the given client request object (block assert)
197
+
198
+ You can also pass a block to `mock_rpc` which will be yielded the request object. This allows more fine-grained assertions on the request object. Also note that if a `:request` option is given (see above), the assert block will be ignored.
199
+
112
200
  ```ruby
113
201
  # Method under test
114
202
  def create_user
@@ -121,7 +209,7 @@ end
121
209
 
122
210
  # spec
123
211
  it 'verifies the request is built correctly' do
124
- mock_remote_service(Proto::UserService, :client) do |given_request|
212
+ mock_rpc(Proto::UserService, :client) do |given_request|
125
213
  given_request.field1.should eq 'rainbows'
126
214
  given_request.field2.should eq 'ponies'
127
215
  end
@@ -134,6 +222,5 @@ Feedback
134
222
 
135
223
  Feedback and comments are welcome:
136
224
 
137
- Web: [rand9.com](http://rand9.com)
138
225
  Twitter: [@localshred](https://twitter.com/localshred)
139
226
  Github: [github](https://github.com/localshred)
@@ -22,7 +22,8 @@ module Protobuf
22
22
  module ClassMethods
23
23
 
24
24
  # Set the service subject. Use this method when the described_class is
25
- # not the class you wish to use with methods like local_rpc. In t
25
+ # not the class you wish to use with methods like local_rpc,
26
+ # request_class, or response_class.
26
27
  #
27
28
  # @example Override subject service for local_rpc calls
28
29
  # describe Foo::BarService do
@@ -33,13 +34,6 @@ module Protobuf
33
34
  # its('response.records') { should have(3).items }
34
35
  # end
35
36
  #
36
- # @example Override subject service for remote_rpc mocks
37
- # describe BarController do
38
- # describe '#index' do
39
- # subject_service { Foo::BarService }
40
- # subject { remote_rpc(:find, request, response) }
41
- # end
42
- # end
43
37
  #
44
38
  def subject_service
45
39
  if block_given?
@@ -62,34 +56,61 @@ module Protobuf
62
56
  #
63
57
  # @example Test a local service method
64
58
  # # Implementation
65
- # module Proto
59
+ # module Services
66
60
  # class UserService < Protobuf::Rpc::Service
67
61
  # def create
68
- # user = User.create_from_proto(request)
69
62
  # if request.name
70
- # respond_with(ProtoRepresenter.new(user))
63
+ # user = User.create_from_proto(request)
64
+ # respond_with(user)
71
65
  # else
72
66
  # rpc_failed 'Error: name required'
73
67
  # end
74
68
  # end
69
+ #
70
+ # def notify
71
+ # user = User.find_by_guid(request.guid)
72
+ # if user
73
+ # Resque.enqueue(EmailUserJob, user.id)
74
+ # respond_with(:queued => true)
75
+ # else
76
+ # rpc_failed 'Error: user not found'
77
+ # end
78
+ # end
75
79
  # end
76
80
  # end
77
81
  #
78
82
  # # Spec
79
- # describe Proto::UserService do
83
+ # describe Services::UserService do
80
84
  # describe '#create' do
81
- # it 'creates a new user' do
82
- # create_request = Proto::UserCreate.new(...)
83
- # service = call_local_service(Proto::UserService, :create, create_request)
84
- # service.response.should eq(some_response_object)
85
+ # subject { local_rpc(:create, request) }
86
+ #
87
+ # context 'when request is valid' do
88
+ # let(:request) { { :name => 'Jack' } }
89
+ # let(:user_mock) { FactoryGirl.build(:user) }
90
+ # before { User.should_receive(:create_from_proto).and_return(user_mock) }
91
+ # it { should eq(user_mock) }
85
92
  # end
86
93
  #
87
- # it 'fails when name is not given' do
88
- # bad_req = { :name => nil }
89
- # service = call_local_service(Proto::UserService, :create, :create_request) do |service|
90
- # # Block is yielded before the method is invoked.
91
- # service.should_receive(:rpc_failed).with('Error: name required')
92
- # end
94
+ # context 'when name is not given' do
95
+ # let(:request) { :name => '' }
96
+ # it { should =~ /Error/ }
97
+ # end
98
+ # end
99
+ #
100
+ # describe '#notify' do
101
+ # let(:request) { { :guid => 'USR-123' } }
102
+ # let(:user_mock) { FactoryGirl.build(:user) }
103
+ # subject { local_rpc(:notify, request) }
104
+ #
105
+ # context 'when user is found' do
106
+ # before { User.should_receive(:find_by_guid).with(request.guid).and_return(user_mock) }
107
+ # before { Resqueue.should_receive(:enqueue).with(EmailUserJob, request.guid)
108
+ # its(:queued) { should be_true }
109
+ # end
110
+ #
111
+ # context 'when user is not found' do
112
+ # before { Resque.should_not_receive(:enqueue) }
113
+ # it { should =~ /Error/ }
93
114
  # end
94
115
  # end
95
116
  # end
@@ -98,13 +119,12 @@ module Protobuf
98
119
  # @param [Protobuf::Message or Hash] request the request message of the expected type for the given method.
99
120
  # @return [Protobuf::Message or String] the resulting protobuf message or error string
100
121
  def local_rpc(rpc_method, request)
101
- request = subject_service.rpcs[rpc_method].request_type.new(request) if request.is_a?(Hash)
122
+ request_klass = request_class(rpc_method)
123
+ request = request_klass.new(request) if request.is_a?(Hash)
102
124
 
103
- outer_request_params = {
104
- :service_name => subject_service.to_s,
105
- :method_name => rpc_method.to_s,
106
- :request_proto => request.serialize_to_string
107
- }
125
+ outer_request_params = { :service_name => subject_service.to_s,
126
+ :method_name => rpc_method.to_s,
127
+ :request_proto => request.serialize_to_string }
108
128
 
109
129
  outer_request = ::Protobuf::Socketrpc::Request.new(outer_request_params)
110
130
  dispatcher = ::Protobuf::Rpc::ServiceDispatcher.new(outer_request)
@@ -140,7 +160,8 @@ module Protobuf
140
160
  #
141
161
  # # spec
142
162
  # it 'verifies the on_success method behaves correctly' do
143
- # mock_rpc(Proto::UserService, :client, response: mock('response_mock', status: 'success'))
163
+ # response_mock = mock('response_mock', :status => 'success')
164
+ # mock_rpc(Proto::UserService, :client, :response => response_mock)
144
165
  # create_user(request).should eq('success')
145
166
  # end
146
167
  #
@@ -160,8 +181,9 @@ module Protobuf
160
181
  #
161
182
  # # spec
162
183
  # it 'verifies the on_success method behaves correctly' do
163
- # mock_rpc(Proto::UserService, :client, error: mock('error_mock', message: 'this is an error message'))
164
- # ErrorReporter.should_receive(:report).with('this is an error message')
184
+ # error_mock = mock('error_mock', :message => 'this is an error message')
185
+ # mock_rpc(Proto::UserService, :client, :error => error_mock)
186
+ # ErrorReporter.should_receive(:report).with(error_mock.message)
165
187
  # create_user(request).should eq('error')
166
188
  # end
167
189
  #
@@ -178,11 +200,11 @@ module Protobuf
178
200
  # # spec
179
201
  # it 'verifies the request is built correctly' do
180
202
  # expected_request = ... # some expectation
181
- # mock_rpc(Proto::UserService, :client, request: expected_request)
203
+ # mock_rpc(Proto::UserService, :client, :request => expected_request)
182
204
  # create_user(request)
183
205
  # end
184
206
  #
185
- # @example Testing the given client request object (explicit assert)
207
+ # @example Testing the given client request object (block assert)
186
208
  # # Method under test
187
209
  # def create_user
188
210
  # request = ... # some operation to build a request on state
@@ -204,26 +226,30 @@ module Protobuf
204
226
  # @param [Class] klass the service class constant
205
227
  # @param [Symbol, String] method a symbol or string denoting the method to call
206
228
  # @param [Hash] callbacks provides expectation objects to invoke on_success (with :response), on_failure (with :error), and the request object (:request)
207
- # @param [Block] assert_block when given, will be invoked with the request message sent to the client method
229
+ # @param [Block] optional. When given, will be invoked with the request message sent to the client method
208
230
  # @return [Mock] the stubbed out client mock
209
- def mock_rpc(klass, method, callbacks={}, &assert_block)
231
+ def mock_rpc(klass, method, callbacks = {})
210
232
  client = double('Client', :on_success => true, :on_failure => true)
211
233
  client.stub(method).and_yield(client)
212
234
 
213
235
  klass.stub(:client).and_return(client)
214
236
 
215
- if callbacks[:request]
237
+ case
238
+ when callbacks[:request] then
216
239
  client.should_receive(method).with(callbacks[:request])
217
- elsif block_given?
240
+ when block_given? then
218
241
  client.should_receive(method) do |given_req|
219
- assert_block.call(given_req)
242
+ yield(given_req)
220
243
  end
221
244
  else
222
245
  client.should_receive(method)
223
246
  end
224
247
 
225
- client.stub(:on_success).and_yield(callbacks[:response]) if callbacks[:response]
226
- client.stub(:on_failure).and_yield(callbacks[:error]) if callbacks[:error]
248
+ success = callbacks[:success] || callbacks[:response]
249
+ client.stub(:on_success).and_yield(success) unless success.nil?
250
+
251
+ failure = callbacks[:failure] || callbacks[:error]
252
+ client.stub(:on_failure).and_yield(failure) unless failure.nil?
227
253
 
228
254
  client
229
255
  end
@@ -1,5 +1,5 @@
1
1
  module Protobuf
2
2
  module RSpec
3
- VERSION = "0.2.2"
3
+ VERSION = "0.2.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protobuf-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-11-20 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: protobuf
16
- requirement: &2153155940 !ruby/object:Gem::Requirement
16
+ requirement: &2152903460 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '2.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2153155940
24
+ version_requirements: *2152903460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2153155220 !ruby/object:Gem::Requirement
27
+ requirement: &2152902320 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '2.8'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2153155220
35
+ version_requirements: *2152902320
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &2153154680 !ruby/object:Gem::Requirement
38
+ requirement: &2152900400 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2153154680
46
+ version_requirements: *2152900400
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yard
49
- requirement: &2153153600 !ruby/object:Gem::Requirement
49
+ requirement: &2152899620 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0.7'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2153153600
57
+ version_requirements: *2152899620
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: redcarpet
60
- requirement: &2153138880 !ruby/object:Gem::Requirement
60
+ requirement: &2152898740 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '2.1'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2153138880
68
+ version_requirements: *2152898740
69
69
  description: Protobuf RSpec helpers for testing services and clients. Meant to be
70
70
  used with the protobuf gem. Decouple external services/clients from each other using
71
71
  the given helper methods.
@@ -97,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
97
  version: '0'
98
98
  segments:
99
99
  - 0
100
- hash: -3082988379435425688
100
+ hash: -623885674851921765
101
101
  required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  none: false
103
103
  requirements:
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  version: '0'
107
107
  segments:
108
108
  - 0
109
- hash: -3082988379435425688
109
+ hash: -623885674851921765
110
110
  requirements: []
111
111
  rubyforge_project: protobuf-rspec
112
112
  rubygems_version: 1.8.15