Disclaimer
This question is subjective. I know we're supposed avoid subjective questions on Stack, but it's something that a) I've been struggling with for a while and don't know where else to ask and b) I think would be considered a constructive subjective question and therefore permitted on Stack.
The Problem
I don't know how to go about testing a convenience method who's only purpose is to call another method on a different object.
As far as I can tell their are two ways to go about this, both with downsides:
-
Retest all of the exact same logic for both the convenience method and the method it calls.
-
Test that the convenience method calls and returns the original method with the expected arguments.
Option 1 makes less sense to me, in particular it doesn't make sense when the origin method (the one that the convenience method wraps) performs slightly complex logic and requires multiple unit tests. Why would I want the exact same logic tested in multiple places? What if that logic changes? Wouldn't it be better to just test that in one place rather than several?
I tend to lean towards option 2, but obviously the glaring red flag their is your testing implementation, not behavior. Which as I've heard time and time again (and tend to agree with for the most part,) you should not do.
An Example Of The Problem
Ok, now on to an example to help clarify what I'm talking about.
I'm currently working on an API of sorts that helps programmers interact with Alexa. The fundamental way to set responses to Alexa in my API is through a response object on an instance of a class called Alexa. For example, to set the output speech to 'hello world' you would use the following:
@alexa = Alexa.new
@alexa.response.set_speech("hello world")
In addition to this, I provide a convenience method on the Alexa class called #say that has the exact same result:
@alexa.say("hello world")
Here's what's going on behind the scenes:
# Alexa class
class Alexa
attr_reader :response
def initialize
@response = Response.new
end
def say(speech)
@response.set_speech(speech)
end
end
# Response class
class Response
attr_reader :response_hash
def initialize
@response_hash = {}
end
def set_speech(speech)
@response_hash[:speech] = speech
end
end
Notice how the Alexa#say method is a wrapper method who's only responsibility is to call response#set_output_speech with the passed in arguments.
Ok, now on to the tests.
For the set_speech method, the testing is easy:
RSpec.describe Response do
describe '#set_speech' do
it 'adds the passed in argument to the response hash under key :speech' do
subject = Response.new
subject.set_speech("hello world")
expect(subject.response_hash[:speech]).to eq("hello world")
end
end
end
For the say method, I'm torn.
I could either retest the behavior of the method, which is not DRY and leads to unit tests that are dependent on other methods and objects:
RSpec.describe Alexa do
describe '#say' do
it 'adds the passed in argument to the response hash of @response under key :speech' do
subject = Alexa.new
subject.say("hello world")
expect(subject.response.response_hash[:speech]).to eq("hello world")
end
end
end
Or I could test that say calls response#set_speech with the provided options, which as far as I can tell is testing implementation, and not behavior:
RSpec.describe Alexa do
describe '#say' do
it 'calls #set_speech on @response' do
subject = Alexa.new
expect(subject.response).to receive(:set_speech).with("hello world")
subject.say("hello world")
end
end
end
My Question
TL;DR - How should I go about testing a convenience method whose sole purpose is to call a different method?
Is it better to retest the behavior and have repetitive and dependent unit tests, or to test that the convenience method calls the original method, and thus test implementation instead of behavior?
Or perhaps there is a third option I haven't come across yet?
Since this is a subjective question I would love more than just a 'use option 1' or 'use option 2' answer, try to explain why you think one technique is better than the other. Maybe even add in a story or two from your experience where you've seen the benefits of one approach over the other. Thanks!