A simple trick to make your classes easier to test

Luís Costa
Runtime Revolution
Published in
5 min readJan 19, 2017

--

I’m a firmly believer that good code is code that can be easily tested. If I can’t easily test a module or a class, it most likely means that the class has too many dependencies and/or too many responsibilities.

Of course, I am guilty of creating such code. Since I’ve started coding professionally, my thought process has drastically changed. I now understand that my code needs to be easily testable and maintainable. I’m not writing code only for me anymore: I’m writing code that is going to be read and used by other developers, so I need to do my best to write the best code that I can.

When I started writing code professionally, I had some idea of what testing was, what TDD was, but had little to no experience in applying these methodologies to real, production code. Needless to say, I wasn’t in the habit of testing my code. This lead me to countless hours of bug hunting that could be easily avoided had I written some tests.

A typical scenario

Over the past few months, I have found myself writing code that needs to communicate with external services (say, with a REST API), parse that result, and spit it out in a format that can be consumed by other modules/classes/service in the code base. Let’s see an example of such code.

# lib/fetcher.rbrequire 'pp'class Fetcher
def initialize(limit_data_items)
@limit_data_items = limit_data_items
@resolver = Resolver.new limit_data_items
end
def get_result
result = @resolver.fetch_net_request
# Some methods that parse the JSON raw result, like:
# result = ResultParser.new(result).parse
pp(result)
end
private class Resolver
require 'httparty'
ENDPOINT = 'https://www.reddit.com/r/news.json'
DEFAULT_LIMIT_ITEMS = 25
def initialize(limit_data_items)
@limit_data_items = parse(limit_data_items)
end
def fetch_net_request
response = HTTParty.get(url)
response.parsed_response
end
private def parse(limit_items_data)
return limit_items_data if limit_items_data.is_a?(Fixnum)
DEFAULT_LIMIT_ITEMS
end
def url
@url ||= "#{ENDPOINT}?limit=#{@limit_data_items}"
end
end
end

Above is an example of a piece of code that is communicating with the reddit API and pulling in the latest news from the subreddit /r/news.

Why this is hard to test

The first thing that should come to your mind when you see code like this, is the defined private class Resolver . This code should ring your code smell bells. Although this may seem a good design decision (maybe this is the only class in the codebase that needs to make external service requests, and thus only this class would need another wrapper to make such requests, which is unlikely), this makes the class Fetcher way harder to test. If the class is hard to test, it is hard to know what is its purpose.

Every time we run some sort of tests on this class, HTTP requests are going to be made. This is not the ideal way of testing your code. What if you’re offline? What if you have a testing machine that runs your tests every x minutes/hour? Or after every commit? You can even get your IP black listed from the service you’re using because of this!

Injected dependencies to the rescue!

A simple solution is to make the Resolver class an injected dependency of the class Fetcher :

class Fetcher
def initialize(limit_data_items, resolver: nil)
@limit_data_items = limit_data_items
@resolver = resolver || Resolver.new limit_data_items
end
...

I now follow this pattern every time I expect a class to use external services or other classes, especially if I know that these external dependencies will make some other external service calls.

Note that with this solution, the class Resolver can be still be a inner, private class, and still make the outer class easy to test. How? Pretty simple! Note that the outer class only instantiates the resolver class unless you provide a specific resolver implementation in the constructor. This means that you can now mock this implementation and inject it in the Fetcher class in your tests.

The tests

In this post I’ll show an example of a unit test for this class. This is important, because this means that we’re not testing the result of external service requests made by theFetcher class. We’re testing the responsibility of this class, which is to fetch the data from some place and apply some transformations to the data.

The simple change made to the constructor of the class made the task of creating unit tests a whole lot easier:

# spec/fetcher_spec.rbrequire 'fetcher'describe Fetcher do
let(:limit_data_items) { 1 }
let(:service_result) { nil } let(:resolver) do
resolver = instance_double('resolver')
expect(resolver).to receive(:fetch_net_request).and_return(service_result)
resolver
end
subject { Fetcher.new(limit_data_items, resolver: resolver).get_result } context 'with empty response' do
let(:service_result) { {} }
it { is_expected.to eq({}) }
end
end

This test is pretty self explanatory! We are testing that our class returns an empty response whenever the external service returns an empty response, because there’s no transformations to apply to the returned data. We mock an instance of the class resolver in the described class, and stub the method fetch_net_request so that it returns some response. By using the let construct of rspec, we can clearly specify what is the service response that the fetcher class is handling in each situation.

For instance, if we want to test the behaviour of our class when the external dependency returns an non empty response:

# spec/fetcher_spec.rbrequire 'fetcher'describe Fetcher do
..
context 'with one object in the response' do
let(:service_result) do
{
data: [
{
id: 1,
contents: 'some contents'
}
]
}
end
it { is_expected.to eq(service_result) }
end
end

(Of course, I’m not doing anything with the service result inside the class, for the sake of simplicity here, so I’m just testing that the method get_result returns the service result unparsed). Remember the line pp(raw_result) inside the method get_result ? Well, guess what, in the context of this test, that method now prints:

> rspec spec/fetcher_spec.rb.{:data=>[{:id=>1, :contents=>"some contents"}]}
.
Finished in 0.00574 seconds (files took 0.07411 seconds to load)
2 examples, 0 failures

Although it is always good practice to test real objects and classes of your system (this will exercise more of your code), sometimes that is not possible or not practical. An example are classes that rely on external services, so they are a good candidate to be mocked in your tests.

How do you make your classes easier to test? I’d love to hear from you! Comment below with your experiences!

Nowadays I work at Runtime Revolution. Working here has been, and continues to be, a great learning experience. I’ve matured professionally as a developer, focusing on building and maintaining large scale Ruby on Rails applications.

--

--