Skip to content

Instantly share code, notes, and snippets.

@chikamichi
Last active September 26, 2016 07:21
Show Gist options
  • Save chikamichi/74f865959ee2471c02a6021a45d0de47 to your computer and use it in GitHub Desktop.
Save chikamichi/74f865959ee2471c02a6021a45d0de47 to your computer and use it in GitHub Desktop.
Test: double example
class Model
# Let's say Elasticsearch needs a JSON representation of the model in order
# to index it. We delegate to a specialized class to generate that JSON:
def as_indexed_json
ModelSerializer.new(self).as_json
end
end
describe Model do
describe '#as_indexed_json' do
let(:model) { Model.new }
let(:serialization) { double }
let(:result) { double }
before do
expect(ModelSerializer).to receive(:new)
.with(model)
.and_return(serialization)
allow(serialization).to receive(:as_json)
.and_return(result)
end
it 'delegates and returns the result from a serializer' do
expect(model.as_indexed_json).to eq result
end
end
end
# Using RSpec syntax.
describe Model do
# In Model#as_indexed_json, we rely on a ModelSerializer class, which is an external
# dependency. For we are solely focused on testing Model, we'd like to avoid
# leveraging that dependency. We can abstract it away using double objects.
#
# The only thing we are interested in is whether the right messages are being
# sent to and received by the right objects. If we trump an actual object, such
# as ModelSerializer, with a double (fake-object), then we must take care of
# letting that double receive and respond to the expected messages.
#
# We can do so by actually setting up expectations (tests) that the messages will
# be sent, received and responded to the right way.
describe '#as_indexed_json' do
let(:model) { Model.new }
# Step 1: use a double for each and every object that is an external dependency
# (eg. serialization) or is provided by one (eg. result).
let(:serialization) { double }
let(:result) { double }
before do
# Step 2: set your expectations when it comes to messages.
# We expect a ModelSerializer to be created. When that happens, let's return
# our double instead of the actual instance. We make an assertion out of this
# expectation, with a precise usage of RSpec's expect method and DSL.
expect(ModelSerializer).to receive(:new)
.with(campaign)
.and_return(serialization)
# We expect the serialization to be casted to JSON. When that happens, let's
# return another double instead of the actual hash. The expectation is "weak",
# that is, we simply stub the result and don't make an assertion out of the
# method being called — but the actual test below will assert the result is
# computed and returned.
allow(serialization).to receive(:as_json)
.and_return(result)
end
it 'delegates and returns the result from a serializer' do
# Step 3: call the targeted method.
# On top of the built-in assertion about ModelSerializer#new, we make sure of
# checking the return form the Model#as_indexed_json, which should be our double.
expect(model.as_indexed_json).to eq result
end
end
end
@merwan
Copy link

merwan commented Sep 26, 2016

Usually, in the before block, I only use allow to setup my doubles. Then, in the test I use expect to verify that my doubles behave as required. Sometimes I would not even expect anything from the double if I'm using it as a stub (see http://martinfowler.com/articles/mocksArentStubs.html for more explanations).
I would write your example as:

describe Model do
  describe '#as_indexed_json' do
    let(:model) { Model.new }
    let(:serializer) { instance_double(ModelSerializer, as_json: result) }
    let(:result) { double }

    before do
      # It's just a setup, I don't care about the way the ModelSerializer is instantiated
      allow(ModelSerializer).to receive(:new) { serializer }
    end

    it 'delegates and returns the result from a serializer' do
      # Here it's an expectation, I want to make sure that my serializer is called with the proper argument
      expect(ModelSerializer).to receive(:new).with(model)
      expect(model.as_indexed_json).to eq result
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment