Write Better Tests with Flexmock

Last updated 2009-05-08.

Nowadays, most developers write at least some automated tests for their code; there are testing libraries available for almost all languages nowadays, and there are many GUI tools supporting them.

Having good tests means that you can trust that your code does what it's supposed to do. It also means that new team members can easily discover what your code is supposed to do, and if you break existing functionality, you'll know because some or all of your tests will fail.

However, in the real world it can be difficult to test classes. Quite often you'll find that while you're trying to test a particular class, you're also testing a bunch of other classes upon which your class depends. And if any of those dependencies should fail, your test will fail, even though your class is working perfectly in isolation!

Fortunately there is a solution, called mocking, and there is a handy dynamic mocking library for Ruby called Flexmock.

Imagine that you're creating a simple command line tool using Ruby (let's call it TextSearch) - an application that downloads a file from the web and searches it for a keyword. A simple first cut could look like this:

searcher.rb:

require 'open-uri'
class Searcher
  def search(uri, pattern)
    text = ''
    open(uri) do |f|
      text = f.read
    end

    return text =~ /#{pattern}/
  end
end


text_search.rb:

#!/usr/bin/ruby
require 'searcher'

uri = ARGV[0]
pattern = ARGV[1]

searcher = Searcher.new
if searcher.search(uri, pattern)
  puts "found!"
end


That's all well and good, but how would you test that the keyword searching is working? Well, you could run it manually:

$ ./text_search http://offbyzero.com/ great
found!


... but a much better way would be to write a test case:

searcher_tests.rb:

require 'text_search.rb'
require 'test/unit'
require 'test/unit/ui/console/testrunner'

class SearcherTests < Test::Unit::TestCase

  def test_search_finds_pattern()
    searcher = Searcher.new
    assert(searcher.search('http://www.offbyzero.com/', 'great'))
  end

end


When the test is run, it passes:

$ ruby searcher_tests.rb
Loaded suite searcher_tests
Started
.
Finished in 0.928491 seconds.

1 tests, 1 assertions, 0 failures, 0 errors


But what happens if the network goes down? At this point the test will fail:

$ ruby searcher_tests.rb
    
  1) Error:
test_search_finds_pattern(FeedMonitorTests):
SocketError: getaddrinfo: Name or service not known
    
1 tests, 0 assertions, 0 failures, 1 errors


But we don't want it to fail, because we're trying to test whether the keyword search actually works. We're not trying to test whether we can download a file from the internet! The way around this problem is to use mocking to 'fake' the file download, so that we can test the behaviour we're really interested in, the searching.

Firstly, we need to move the downloading functionality out into a separate class:

downloader.rb:

require 'open-uri'

class Downloader

  def download(uri)
    text = ''
    open(uri) do |f|
      text = f.read
    end
    return text
  end

end


searcher.rb:

require 'downloader'

class Searcher
  
  attr_accessor :downloader

  def search(uri, pattern)
    text = @downloader.download(uri)
    return text =~ /#{pattern}/
  end
end


searcher_tests.rb:

require 'searcher.rb'
require 'test/unit'
require 'test/unit/ui/console/testrunner'

class FeedMonitorTests < Test::Unit::TestCase

  def test_search_finds_pattern()
    searcher = Searcher.new
    searcher.downloader = Downloader.new

    assert(searcher.search('http://www.offbyzero.com/', 'great'))
  end

end


At this point the searching functionality all lives in the Searcher class, and the downloading functionality lives in the Downloader class. The next step is to mock the Downloader class so we can test the Searcher in isolation.

First, install the flexmock library with:

$ sudo gem install flexmock


Once that's done, you can use Flexmock to create the mock Downloader as part of the test:

searcher_tests.rb:

require 'rubygems'
require 'searcher.rb'
require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'flexmock/test_unit'

class FeedMonitorTests < Test::Unit::TestCase

  def test_search_finds_pattern()

    mock_downloader = flexmock(
      :downloader, 
      :download => "Some text that is pretty great.")

    searcher = Searcher.new
    searcher.downloader = mock_downloader

    assert(searcher.search('http://www.offbyzero.com/', 'great'))
  end

end


The significant change is that instead of creating a new Downloader, we're now using flexmock to create a mock downloader. This line:

mock_downloader = flexmock(
  :downloader, 
  :download => "Some text that is pretty great.")


... tells flexmock to create a mock object that expects a call to a method called “download”, and when it receives that call, to return the text “Some text that is pretty great.” You can then use this mock Downloader instead of a real Downloader.

When you run the test with the mock Downloader you'll also notice another advantage of mocking: performance. Now that the Searcher under test is using a mock Downloader it doesn't really have to go and download a file from the internet, so it's much faster than before.

This introduction only scratches the surface of what mocking can do, but it should serve to illustrate why you would want to do it. By mocking your dependencies, you can ensure that your tests are testing only the class you want to test, and you can write tests that are faster, more reliable, and easier to read.