Dead Simple Mocks For Unit Tests

Yesterday I was testing a Helper (module) I had written for a Rails app I am working on. I wanted to test the helper directly, and not deal with the whole controller and request/response loop just to verify some simple methods. So I started with something like the following:

class TrackingHelperTest < ActiveSupport::TestCase
    include TrackingHelper

    def test_record_event
        ...unimportant...
    end
end

I ran into a problem almost immediately. Some of the methods in this Helper required access to the request object. At first I thought I went down the wrong path, and would need to test this Helper in the context of a controller. Then it hit me, mock the request up. But do I really need a whole class to store and share some instance variables? After pondering this for a few moments I ended up with something like this:

class TrackingHelperTest < ActiveSupport::TestCase
    include TrackingHelper

    # Mocks
    MockRequest = Struct.new(:query_parameters, :path, :headers, :remote_ip, :path_parameters, :query_string)
    attr_accessor :request

    def setup
        @request = MockRequest.new({}, "/some/location", {"HTTP_REFERER" => "somesite.com"}, "1.1.1.1", {:controller => "some", :action => "location"}, "")
    end

    def test_record_event
        ...unimportant...
    end
end

Struct’s are simple to use and allow for quick and easy mocking in unit tests. Try it out in your code next time you don’t want to build a full class to do something simple.

Random Thoughts Involving Computers

This is the output from a cool command I found on ones zeros majors and minors

history 1000 | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' \
| sort -rn | head
124 rake
106 svn
84 ss
36 cd
30 git
26 rm
26 ls
16 sc
10 mate
5 psql

How about you?

And, again from ozmm is the try command. I think I’m going to start adding this to my projects, seems really simple and helpful.

class Object
  ##
  #   @person ? @person.name : nil
  # vs
  #   @person.try(:name)
  def try(method)
    send method if respond_to? method
  end
end

GitHub and Me

This weekend a fellow member of Rochester On Rails was nice enough to share an invite to Git Hub. Check out my account, I just published two little plugins I use all the time at work and at home when building Rails apps.

The first is Fixture Manager. This plugin adds a few methods to ActiveRecord classes allowing you to save database contents to YAML (either in your test/fixtures folder, or in cms/fixtures). The plugin also provides a few Rake tasks that wrap these methods as well as a simple static class (FixtureManager) giving you two options as to how you want to save and load your data.

The second is Rcov Task. This plugin contains almost no code, its a Rake task that simplifies running your test suite with rcov (and opening your browser if your in Mac OS X).

Sake and Database Shells

If you haven’t installed Sake yet, stop whatever it is you’re doing and get it running on your system.

Ok, with that done you’ll probably want some sake tasks to actually make this new tool useful. Below is an updated version of one my favorite tasks.

desc 'Launches the database shell using the values defined in config/database.yml'
task 'db:shell' => [ 'environment' ] do
  config = ActiveRecord::Base.configurations[(RAILS_ENV or "development")]
  command = ""
  case config["adapter"]
  when "mysql" then
    command << "mysql "
    command << "--host=#{(config["host"] or "localhost")} "
    command << "--port=#{(config["port"] or 3306)} "
    command << "--user=#{(config["username"] or "root")} "
    command << "--password=#{(config["password"] or "")} "
    command << config["database"])
  when "postgresql" then
    command << "export PGPASSWORD=#{config["password"]} && " unless config["password"].blank?
    command << "psql "
    command << "-h #{(config["host"])} " unless config ["host"].blank?
    command << "-p #{(config["port"])} ") unless config ["port"].blank?
    command << "-U #{(config["username"])} " unless config["username"].blank?
    command << config["database"]
  when "sqlite3"
    command << "sqlite3 #{config["database"]}"
  else
    command << "echo Unsupported database adapter: #{config["adapter"]}"
  end
  system(command)
end

With the above task you be in any RAILS_ROOT and type sake db:shell and be automatically placed into the appropriate database shell with your username/password/host/port setup already. You can even do sake db:shell RAILS_ENV=production.

I can’t take credit for this task, in fact I originally found it on the Sake Bomb blog post by Err The Blog. The link found in the comments on that page no longer works, but some googling turned up the source code elsewhere. I’ve added Postgres and sqlite3 to the mix as it only supported MySQL originally. If you know who the original author is please let me know as I would like to credit them.

Mocking Net::HTTP For Testing

Quite often at work I find myself having to write code (and therefore tests) that use the Net::HTTP class to read XML from some external REST based service. In this brief article I will explain two minor updates I perform on the Net:HTTP and File classes when in my testing environment.

Most of the services I interact with provide QA and Staging environments along with their Production system. Often times though I am performing other actions with other external services based on the data I am reading from the XML document. Because of this I prefer to mock up the XML response with data that will allow me to test all cases.

The following code uses a few Rails only extensions like alias_method_chain. It is quite simple to factor these out, but I will leave that as a challenge to the reader.

module Net
   class HTTP
     def get_with_file(path, *args)
       case File.basename(path)
       when /activations/
         mock_path = File.join(File.dirname(__FILE__),  "activations.xml")
         return File.open(mock_path)
       else
         get_without_file(path, *args)
       end
     end
     alias_method_chain :get, :file
   end
end

The above class opens Net::HTTP and adds the get_with_file method. Its processing is fairly simple, it looks at the basename of the path provided and uses that to match inside a case statement. Using this case statement I can now open a file, or proceed to another method (get_without_file in this case). Following this method definition is a call to alias_method_chain that renames Net::HTTP.get to Net::HTTP::get_without_file, and links Net::HTTP.get to Net::HTTP::get_with_file.

When using a file to represent an HTTP response object you would normally use the following code and be done.

class File
  alias_method :body, :read
end

This will provide the desired results and make the File appear to be a simple HTTP response. The problem with the simple rewrite to File is that if you call response.body more than once, you will receive nil after the first try. This is because you have reached the end of the file the first time you read through it. To combat this problem I use the following code instead of the simple code above.

class File
   def read_with_memory
     @file_contents ||= read_without_memory
     return @file_contents
   end
   alias_method_chain :read, :memory
   alias_method :body, :read_with_memory

   def header
     {
       'status' => "201 Created",
       'location' => "http://somethingunimportant.com/orders/1"
     }
   end

   def code
     "201"
   end
end

The header and code methods aren’t at all required, but were needed due to the calling code of mine using them to verify the Net::HTTP response object before starting to process the data. The read_with_memory method, like the other two is the important change. This method reads the file into an instance variable and saves it. Each subsequent call returns the instance variable, and not the end of the File.

Adding a sitemap.xml file to Mephisto

Should be an easy task, right? We’ll it wasn’t bad, but it could be easier. After a bunch of googling and failed attempts I settled on this mephisto sitemap plugin. According to the blog it should be easy, just script/plugin install and away you go…

Unfortunately, this doesn’t work out of the box, a few very minor changes are required. I found both of these in the comments on the blog release, but thought it might be helpful for someone else to have a concise walk through here.

Before beginning though, you might want to check out piston if you’re a subversion user.

The first step is to add the follow line to lib/mephisto/routing.rb

      map.connect '/sitemap.xml', :controller => 'sitemap', :action => 'index'

For some unexplained reason the add_route call in vendor/plugins/mephisto_sitemap/lib/sitemap.rb doesn’t seem to be executing properly. If you’re worried you can comment it out, but it doesn’t seem to be causing an issue.

Next, you need to make a change to the second line in vendor/plugins/mephisto_sitemap/lib/sitemap_controller.rb

  self.view_paths = File.join(File.dirname(__FILE__),'../', 'views')

This is required due to a change in edge rails where template_root has been switched to views_path.

Now you should be ready to go. Restart your server and hit up **/sitemap.xml** to see your new plugin in action. Also, don’t forget to visit Google’s Sitemaps for Webmasters and add your sitemap.

Rambling one post at a time