protobuf-rspec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +115 -0
- data/Rakefile +1 -0
- data/lib/protobuf/rspec.rb +2 -0
- data/lib/protobuf/rspec/helpers.rb +197 -0
- data/lib/protobuf/rspec/version.rb +5 -0
- data/protobuf-rspec.gemspec +26 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
protobuf-rspec gem
|
2
|
+
==================
|
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.
|
5
|
+
|
6
|
+
**Note:** Tested to work with the [protobuf gem](https://rubygems.org/gems/protobuf) (>= 1.0).
|
7
|
+
|
8
|
+
Mocking Client Requests (Outside-In test of the service methods)
|
9
|
+
----------------------------------------------------------------
|
10
|
+
|
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.
|
12
|
+
|
13
|
+
Given the service implementation below:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
module Proto
|
17
|
+
class UserService < Protobuf::Rpc::Service
|
18
|
+
def create
|
19
|
+
user = User.create_from_proto(request)
|
20
|
+
self.response = ProtoRepresenter.new(user).to_proto
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
This could be one way to test the implementation while ignoring the RPC backend:
|
27
|
+
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
describe Proto::UserService do
|
31
|
+
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)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Mocking Service Responses
|
42
|
+
-------------------------
|
43
|
+
|
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. If you would like to test the object that is given as a request you should provide a :request object.
|
45
|
+
|
46
|
+
### Testing the client on_success callback
|
47
|
+
```ruby
|
48
|
+
# Method under test
|
49
|
+
def create_user(request)
|
50
|
+
status = 'unknown'
|
51
|
+
Proto::UserService.client.create(request) do |c|
|
52
|
+
c.on_success do |response|
|
53
|
+
status = response.status
|
54
|
+
end
|
55
|
+
end
|
56
|
+
status
|
57
|
+
end
|
58
|
+
...
|
59
|
+
|
60
|
+
# spec
|
61
|
+
it 'verifies the on_success method behaves correctly' do
|
62
|
+
mock_remote_service(Proto::UserService, :client, response: mock('response_mock', status: 'success'))
|
63
|
+
create_user(request).should eq('success')
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Testing the client on_failure callback
|
68
|
+
```ruby
|
69
|
+
# Method under test
|
70
|
+
def create_user(request)
|
71
|
+
status = nil
|
72
|
+
Proto::UserService.client.create(request) do |c|
|
73
|
+
c.on_failure do |error|
|
74
|
+
status = 'error'
|
75
|
+
ErrorReporter.report(error.message)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
status
|
79
|
+
end
|
80
|
+
...
|
81
|
+
|
82
|
+
# spec
|
83
|
+
it 'verifies the on_success method behaves correctly' do
|
84
|
+
mock_remote_service(Proto::UserService, :client, error: mock('error_mock', message: 'this is an error message'))
|
85
|
+
ErrorReporter.should_receive(:report).with('this is an error message')
|
86
|
+
create_user(request).should eq('error')
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
### Testing the given client request object
|
91
|
+
```ruby
|
92
|
+
# Method under test
|
93
|
+
def create_user
|
94
|
+
request = ... # some operation to build a request on state
|
95
|
+
Proto::UserService.client.create(request) do |c|
|
96
|
+
...
|
97
|
+
end
|
98
|
+
end
|
99
|
+
...
|
100
|
+
|
101
|
+
# spec
|
102
|
+
it 'verifies the request is built correctly' do
|
103
|
+
expected_request = ... # some expectation
|
104
|
+
mock_remote_service(Proto::UserService, :client, request: expected_request)
|
105
|
+
create_user(request)
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
Feedback
|
110
|
+
|
111
|
+
Feedback and comments are welcome:
|
112
|
+
|
113
|
+
Web: [rand9.com](http://rand9.com)
|
114
|
+
Twitter: [@localshred](https://twitter.com/localshred)
|
115
|
+
Github: [github](https://github.com/localshred)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'protobuf/rpc/rpc.pb'
|
2
|
+
|
3
|
+
# RSpec Helpers designed to give you mock abstraction of client or service layer.
|
4
|
+
# Require as protobuf/rspec/helpers and include into your running RSpec configuration.
|
5
|
+
#
|
6
|
+
# @example Configure RSpec to use Protobuf Helpers
|
7
|
+
# require 'protobuf/rspec'
|
8
|
+
# RSpec.configure do |config|
|
9
|
+
# config.include Protobuf::Rspec::Helpers
|
10
|
+
# end
|
11
|
+
module Protobuf
|
12
|
+
module RSpec
|
13
|
+
module Helpers
|
14
|
+
|
15
|
+
ClientMock = Struct.new("ClientMock", :request, :response, :error)
|
16
|
+
|
17
|
+
# Call a local service to test responses and behavior based on the given request.
|
18
|
+
# Should use to outside-in test a local RPC Service without testing the underlying socket implementation.
|
19
|
+
#
|
20
|
+
# @example Test a local service method
|
21
|
+
# # Implementation
|
22
|
+
# module Proto
|
23
|
+
# class UserService < Protobuf::Rpc::Service
|
24
|
+
# def create
|
25
|
+
# user = User.create_from_proto(request)
|
26
|
+
# self.response = ProtoRepresenter.new(user).to_proto
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Spec
|
32
|
+
# describe Proto::UserService do
|
33
|
+
# describe '#create' do
|
34
|
+
# it 'creates a new user' do
|
35
|
+
# create_request = Proto::UserCreate.new(...)
|
36
|
+
# client = call_local_service(Proto::UserService, :create, create_request)
|
37
|
+
# client.response.should eq(some_response_object)
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @param [Class] klass the service class constant
|
43
|
+
# @param [Symbol, String] method a symbol or string denoting the method to call
|
44
|
+
# @param [Protobuf::Message] request the request message of the expected type for the given method
|
45
|
+
# @param [Fixnum] timeout force timeout on service call
|
46
|
+
# @return [ClientMock] an object which contains the request and potential response or error objects
|
47
|
+
def call_local_service(klass, method, request, timeout=5)
|
48
|
+
service = klass.new
|
49
|
+
response = service.rpcs[method].response_type.new
|
50
|
+
service.stub(:request).and_return(request)
|
51
|
+
service.stub(:response).and_return(response)
|
52
|
+
def service.response= res
|
53
|
+
@response = res
|
54
|
+
self.stub(:response).and_return(@response)
|
55
|
+
end
|
56
|
+
def service.rpc_failed message
|
57
|
+
@custom_error = message
|
58
|
+
send_response
|
59
|
+
end
|
60
|
+
def service.send_response
|
61
|
+
@send_response_called = true
|
62
|
+
end
|
63
|
+
|
64
|
+
client_mock = ClientMock.new
|
65
|
+
client_mock.request = request
|
66
|
+
|
67
|
+
begin
|
68
|
+
yield(service) if block_given?
|
69
|
+
service.__send__("rpc_#{method}")
|
70
|
+
rescue
|
71
|
+
client_mock.error = $!
|
72
|
+
else
|
73
|
+
client_mock.error = service.instance_variable_get(:@custom_error)
|
74
|
+
ensure
|
75
|
+
if client_mock.error.nil?
|
76
|
+
if service.instance_variable_get(:@async_responder)
|
77
|
+
Timeout.timeout(timeout) do
|
78
|
+
sleep 0.5 until service.instance_variable_get(:@send_response_called)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
client_mock.response = service.instance_variable_get(:@response) || response
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
client_mock
|
86
|
+
end
|
87
|
+
alias :call_service :call_local_service
|
88
|
+
|
89
|
+
# Create a mock service that responds in the way you are expecting to aid in testing client -> service calls.
|
90
|
+
# In order to test your success callback you should provide a :response object. Similarly, to test your failure
|
91
|
+
# callback you should provide an :error object. If you would like to test the object that is given as a request
|
92
|
+
# you should provide a :request object.
|
93
|
+
#
|
94
|
+
# @example Testing the client on_success callback
|
95
|
+
# # Method under test
|
96
|
+
# def create_user(request)
|
97
|
+
# status = 'unknown'
|
98
|
+
# Proto::UserService.client.create(request) do |c|
|
99
|
+
# c.on_success do |response|
|
100
|
+
# status = response.status
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
# status
|
104
|
+
# end
|
105
|
+
# ...
|
106
|
+
#
|
107
|
+
# # spec
|
108
|
+
# it 'verifies the on_success method behaves correctly' do
|
109
|
+
# mock_remote_service(Proto::UserService, :client, response: mock('response_mock', status: 'success'))
|
110
|
+
# create_user(request).should eq('success')
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# @example Testing the client on_failure callback
|
114
|
+
# # Method under test
|
115
|
+
# def create_user(request)
|
116
|
+
# status = nil
|
117
|
+
# Proto::UserService.client.create(request) do |c|
|
118
|
+
# c.on_failure do |error|
|
119
|
+
# status = 'error'
|
120
|
+
# ErrorReporter.report(error.message)
|
121
|
+
# end
|
122
|
+
# end
|
123
|
+
# status
|
124
|
+
# end
|
125
|
+
# ...
|
126
|
+
#
|
127
|
+
# # spec
|
128
|
+
# it 'verifies the on_success method behaves correctly' do
|
129
|
+
# mock_remote_service(Proto::UserService, :client, error: mock('error_mock', message: 'this is an error message'))
|
130
|
+
# ErrorReporter.should_receive(:report).with('this is an error message')
|
131
|
+
# create_user(request).should eq('error')
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# @example Testing the given client request object
|
135
|
+
# # Method under test
|
136
|
+
# def create_user
|
137
|
+
# request = ... # some operation to build a request on state
|
138
|
+
# Proto::UserService.client.create(request) do |c|
|
139
|
+
# ...
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
# ...
|
143
|
+
#
|
144
|
+
# # spec
|
145
|
+
# it 'verifies the request is built correctly' do
|
146
|
+
# expected_request = ... # some expectation
|
147
|
+
# mock_remote_service(Proto::UserService, :client, request: expected_request)
|
148
|
+
# create_user(request)
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# @param [Class] klass the service class constant
|
152
|
+
# @param [Symbol, String] method a symbol or string denoting the method to call
|
153
|
+
# @param [Hash] cb_mocks provides expectation objects to invoke on_success (with :response), on_failure (with :error), and the request object (:request)
|
154
|
+
# @return [Mock] the stubbed out client mock
|
155
|
+
def mock_remote_service(klass, method, cb_mocks={})
|
156
|
+
cb_mocks = {:error => mock('error', :message => nil, :code => nil)}.merge(cb_mocks)
|
157
|
+
klass.stub(:client).and_return(client = mock('Client'))
|
158
|
+
client.stub(method).and_yield(client)
|
159
|
+
if cb_mocks[:request]
|
160
|
+
client.should_receive(method).with(cb_mocks[:request])
|
161
|
+
else
|
162
|
+
client.should_receive(method)
|
163
|
+
end
|
164
|
+
|
165
|
+
if cb_mocks[:response]
|
166
|
+
client.stub(:on_success).and_yield(cb_mocks[:response])
|
167
|
+
else
|
168
|
+
client.stub(:on_success)
|
169
|
+
end
|
170
|
+
|
171
|
+
if cb_mocks[:error]
|
172
|
+
client.stub(:on_failure).and_yield(cb_mocks[:error])
|
173
|
+
else
|
174
|
+
client.stub(:on_failure)
|
175
|
+
end
|
176
|
+
client
|
177
|
+
end
|
178
|
+
alias :mock_service :mock_remote_service
|
179
|
+
|
180
|
+
# Stubs out a protobuf message object internals so that we can just test the needed fields.
|
181
|
+
# It's debatable if this is actually helpful since the protobuf message is so lean in the first place.
|
182
|
+
#
|
183
|
+
# @param [Class, String] klass the message class constant or the message name
|
184
|
+
# @param [Hash] stubs the stubbed fields and values
|
185
|
+
# @return [OpenStruct] the mocked message
|
186
|
+
def mock_proto(klass, stubs={})
|
187
|
+
proto_instance = OpenStruct.new({:mock_name => klass}.merge(stubs))
|
188
|
+
proto_instance.stub!(:has_field? => true)
|
189
|
+
proto = mock(klass)
|
190
|
+
proto.stub!(:tap).and_yield(proto_instance)
|
191
|
+
klass.stub!(:new).and_return(proto)
|
192
|
+
proto_instance
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "protobuf/rspec/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "protobuf-rspec"
|
7
|
+
s.version = Protobuf::RSpec::VERSION
|
8
|
+
s.authors = ["BJ Neilsen"]
|
9
|
+
s.email = ["bj.neilsen@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/localshred/protobuf-rspec"
|
11
|
+
s.summary = %q{Protobuf RSpec helpers for testing services and clients. Meant to be used with the protobuf gem. Decouple external services/clients from each other using the given helper methods.}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "protobuf-rspec"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_runtime_dependency "protobuf", "~> 1.1"
|
23
|
+
s.add_runtime_dependency "rspec", "~> 2.8"
|
24
|
+
s.add_development_dependency "yard", "~> 0.7"
|
25
|
+
s.add_development_dependency "redcarpet", "~> 2.1"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protobuf-rspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- BJ Neilsen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-10 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: protobuf
|
16
|
+
requirement: &2160846800 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2160846800
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2160845600 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.8'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2160845600
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yard
|
38
|
+
requirement: &2160844920 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.7'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2160844920
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: redcarpet
|
49
|
+
requirement: &2160843920 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.1'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2160843920
|
58
|
+
description: Protobuf RSpec helpers for testing services and clients. Meant to be
|
59
|
+
used with the protobuf gem. Decouple external services/clients from each other using
|
60
|
+
the given helper methods.
|
61
|
+
email:
|
62
|
+
- bj.neilsen@gmail.com
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- .gitignore
|
68
|
+
- Gemfile
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/protobuf/rspec.rb
|
72
|
+
- lib/protobuf/rspec/helpers.rb
|
73
|
+
- lib/protobuf/rspec/version.rb
|
74
|
+
- protobuf-rspec.gemspec
|
75
|
+
homepage: http://github.com/localshred/protobuf-rspec
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project: protobuf-rspec
|
95
|
+
rubygems_version: 1.8.10
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Protobuf RSpec helpers for testing services and clients. Meant to be used
|
99
|
+
with the protobuf gem. Decouple external services/clients from each other using
|
100
|
+
the given helper methods.
|
101
|
+
test_files: []
|
102
|
+
has_rdoc:
|