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.