Ruby: The Unit Test-Friendly Language

Accelerate your tests with test doubles

Let’s say you have a Ruby class that retrieves the contents of web pages, and you need to write a unit test for it…

class Spider

  attr_accessor :address, :path

  def get_response
    response = Net::HTTP.get_response(@address, @path)
  end

  def get_body
    get_response.body
  end

end

You’ve tested the get_response method, and now you need to test get_body. You’re using MiniTest, a unit testing framework that comes standard with Ruby.

class TestSpider < MiniTest::Unit::TestCase

  def test_get_body

    spider = Spider.new
    spider.address = 'programming.oreilly.com'
    spider.path = '/2014/02/why-ruby-blocks-exist.html'
    assert spider.get_body == '<h1>Hi!'

  end

end

You create a Spider instance, assign it a page to retrieve, and at the end of the test, assert that the HTTP response matches your expected value. Simple enough.

Two problems

But there’s two problems with this test:

  • Your Spider instance is making a real network request for the page, slowing the test and incurring network overhead for both you and the host you visit.
  • The page could easily change on the remote side. You can use the current page contents in the test assertion, but it might need to be changed in the future.

In fact, the latter issue is causing your test to fail right now, because the real page doesn’t match the simplified HTML in your test.

Finished tests in 3.767214s, 0.2654 tests/s, 0.2654 assertions/s.

  1) Failure:
test_get_body(TestSpider) [-:26]:
Failed assertion, no message given.

The output above also shows that this single test took nearly 4 seconds to complete while it waited for the HTTP response. This test is going to be run hundreds or thousands of times over your product’s life cycle. You want it to be as fast, efficient, and stable as possible. In its current state, it’s just not going to work.

A test double object, using Ruby’s “duck typing”

But we’re testing the get_body method right now, not the get_response method. Who says the get_response method has to retrieve the real page? Maybe we could return some kind of fake response object – a test double.

In strongly-typed languages, that’s more difficult than it sounds. They’re expecting an object of a particular type – one that implements all of the methods the real object does. Only one method may actually get called on your double, but unless it implements all the others, you won’t be able to pass it to the method you’re testing. In such languages, it’s common to have test double classes with dozens of nearly-empty methods, just so your tests will compile. Complex third-party object mocking frameworks such as Moq (for C#) and jMock (for Java) have arisen to help with this problem, but you still have to install one and learn to use it.

Ruby makes it much easier on you. It follows the duck typing principle, as in “if it walks like a duck and quacks like a duck, I’m going to treat that object as if it were a duck”. Ruby doesn’t care what an object’s type is, it only cares that it has the necessary behavior. For purposes of this discussion, duck typing means that you can use any object to test the get_body method, as long as it has a body method of its own (because that’s the method that’s going to get called on it during the test).

...
  def get_body
    get_response.body
  end
...

How do we get a test double with a body method? Well, there are Ruby libraries out there (such as the venerable RSpec) that will create doubles for you, but for an example this simple, we don’t need one. Ruby classes are so easy to create, that we can make a FakeResponse class with a body attribute in just three lines of code:

class FakeResponse
  attr_accessor :body
end

When we create an instance of FakeResponse, we can assign any expected value we need to the body attribute, and get it back out again:

response = FakeResponse.new
response.body = "<h1>Hi!</h1>"
puts response.body # Prints "<h1>Hi!".

There it is – an object where you can call the body method and get the return value we need. And thanks to duck typing, we can substitute it for the return value from get_response, and the Ruby interpreter won’t complain.

Stubbing a method to return our fake response

But that brings us to our next problem: get_response is called directly within get_body. We need to make get_response return a FakeResponse instead of actually visiting the page and returning a real response – we need to stub out the method.

Fortunately, Ruby has another feature that can help us: singleton methods. A singleton method is a method that is defined on one single object – it doesn’t exist anywhere else.

my_string = "ruby is so cool"
def my_string.sort_words
  self.split(' ').sort.join(' ')
end
puts my_string.sort_words # Prints "cool is ruby so"
"don't have this method".sort_words # Error: undefined method

You can also use singleton methods to override an existing method on an object. Normally, the length method on a String object gives its length in characters, but we can override it, just for one object, to give its length in words:

my_string = "ruby is so cool"
def my_string.length
  self.split(' ').length
end
puts my_string.length # Prints "4"
puts "haven't overridden this method".length # Prints "30", like normal

Let’s use singleton methods to stub out the get_response method on our Spider instance. Currently, the method is making a real HTTP request, and returning a real response object. Let’s override get_response, just on the instance in our test. We’ll have it skip making the network request, and return a FakeResponse object.

class TestSpider < MiniTest::Unit::TestCase

  def test_get_body

    spider = Spider.new
    spider.address = 'programming.oreilly.com'
    spider.path = '/2014/02/why-ruby-blocks-exist.html'

    def spider.get_response
      response = FakeResponse.new
      response.body = '<h1>Hi!'
      response
    end

    assert spider.get_body == '<h1>Hi!'

  end

end

Thanks to duck typing, the get_body method won’t care that get_response is returning a FakeResponse, as long as it can call a body method on it. And the value returned by body exactly matches the expectation in our test.

  def get_body
    get_response.body
  end

Let’s try running it:

Finished tests in 0.000660s, 1515.1515 tests/s, 1515.1515 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

It’s passing, and it’s much faster (far less than a second)! No unnecessary network requests, and a short, predictable value to test for. We didn’t even have to use any fancy mock object libraries.

We all want to write more, better, faster-running tests. That’s why Ruby’s duck typing and singleton methods are so great – they make writing tests easy.

tags: