ObjectProtocol
- Goals
- What’s An Object Protocol?
- Using Object Protocols
- A Simple Logger
- How Does This All Work, Anyway?
- Interaction with
#method_missing
- Interaction with
- Installation
- Development
- Contributing
- Code of Conduct
Goals
-
Write message expectation tests with less boilerplate than when using RSpec spies
-
Resulting protocols should be usable as documentation
-
Be able to specify which object sent the message and which object received it (traditional message expectation test only allow the latter)
What’s An Object Protocol?
Protocols are the types of an OO program. They structure and order the messages passed between communicating agents.
Using Object Protocols
A Simple Logger
Given a unrealistically simple logger:
class UnrealisticLogger
def initialize(device)
@device = device
end
def log(message)
@device << message
end
def rotate
@device.shift
end
end
we can write a protocol for what happens when we call #log
on an instance of UnrealisticLogger
UnrealisticLoggingProtocol = ObjectProtocol.new(:device, :logger) do # (1)
logger.sends(:<<).to(device) # (2)
end
-
we need to tell the protocol the names of the participants
-
we switch to using local variables here instead of the symbols
Then, we can test this protocol:
require 'object_protocol/rspec'
RSpec.describe UnrealisticLoggingProtocol do
it "is satisfied by calling #log on the logger" do
device = []
logger = UnrealisticLogger.new(device) # (1)
UnrealisticLoggingProtocol.bind( # (2)
device: device,
logger: logger,
)
expect(UnrealisticLoggingProtocol).to be_satisfied_by do
logger.log("message") # (3)
end
end
end
-
instantiate the participants
-
bind participants to the protocol
-
test an execution against the protocol
Ok, but what was all that?
Well, we created a protocol that we can use as documentation, and tested it with a lot less fanfare and boilerplate than you’d need with traditional message expectation tests!
In real life, you’d use an actual object defined in your codebase in place of a fake logger, but you’d probably inject fake or stub collaborator objects to avoid side-effects.
How Does This All Work, Anyway?
During an execution (the block passed to satisfied_by
), we spy on every public method (except #send
and #object_id
because of the warnings) defined on each bound participant object. We record the message, the sender, the receiver, and the arguments (if any) that were passed. Then, we invoke the actual method behavior defined by that object.
Interaction with #method_missing
#method_missing
is rarely a message that is sent from one object to another. We currently record the name of the missing method as the "sent message" and record any arguments beyond that message name, as well as the sender and receiver.
Installation
Add this line to your application’s Gemfile:
gem 'object_protocol'
And then execute:
$ bundle
Or install it yourself as:
$ gem install object_protocol
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/yarmiganosca/object_protocol. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Code of Conduct
Everyone interacting in the ObjectProtocol project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.