Mocha loading order

Manfred Stienstra, 23 Nov 2009, 14:07 in ruby on rails, testing, and broken (edit).

From version 0.9.8 onwards Mocha wants to be loaded after the test framework. I suspect this has something to do with detecting how to hook into the framework so it can run its expectations.

If you don’t do this, Mocha will not define its methods on the test case or context and you will get a load error.

NoMethodError: undefined method `mock' for #<#<Class>

Note that config.gem immediately loads the gem you’re configuring so you will also have to require those in the correct order.

config.gem 'test-spec'
config.gem 'mocha'

No comments yet

Collecting with Moksi

Manfred Stienstra, 18 Apr 2009, 14:54 in testing and javascript (edit).

For some reason language designers and implementers usually do a bad job a designing the API of the standard library. That’s why I’m pretty happy with the bareness of the JavaScript standard library: it leaves room for libraries like Prototype and JSQuery to step in and tailor the API’s to the task the everyday programmer is trying to achieve.

I do mind that JavaScript doesn’t allow you to introspect or invoke event handlers because that’s bad for people who like to write tests.

Getting your test to introspect observers is not that hard, you just collect all the calls to Event.observe and element.observe.

Event.observers = [];
Moksi.stub(Event, 'observe', function(element, name, handler) {
  Event.observers.push([element, name, handler]);
});
$$('input').each(function(element) {
  Moksi.stub(element, 'observe', function(name, handler) {
    Event.observers.push([element, name, handler]);
  });
});

Now you can look in Event.observers and see what handlers got defined on which elements.

this.assertObserves = function(expectedElement, expectedName) {
  this.assert(Event.observers.any(function(args) {
    return (args[0] == expectedElement) && (args[1] == expectedName);
  }));
};

this.assertObserves($('input[name="artist_name"]'), 'blur');

And you can also call them from a test to make sure they work as expected. To keep the example short we assume you don’t have to pass arguments to the handler.

this.callObserver = function(element, eventName) {
  var observer = Event.observers.detect(function(args) {
    return (args[0] == element) && (args[1] == eventName);
  });
  observer[2]();
};

this.callObserver($('input[name="artist_name"]'), 'blur');

You can find Moksi on GitHub and testunit.js is part of script.aculo.us.

No comments yet

Stubbing with Moksi

Manfred Stienstra, 20 Mar 2009, 00:02 in testing and javascript (edit).

In the summer of 2008 I wrote a small mocking and stubbing library for JavaScript called Moksi. I used it a little bit in a project and then forgot about it. When we picked up the project again a few weeks ago I was happy to rediscover Moksi. Because there isn’t much written about testing in JavaScript, I figured it might be a good idea to share some tips and tricks.

We do most of our testing with unittest.js which was developed as part of the testsuite for script.aculo.us. If you’re not familiar with it: Thomas Fuchs did a nice presentation at RailsConf 2006 about it. You might want to read up if you don’t know what I’m taling about.

So now you have assertions galore, but what do you do when you don’t want to recreate an entire scenario in the DOM just to test one or two things? You stub them.

Suppose you want to test the following function code:
var Autosave = Class.create({
  initialize: function(form) {
    this.form = $(form);
  },
  
  installObserver: function() {
    this.form.observe('change', function() {
      this.saveAsDraft();
    });
  },
  
  saveAsDraft: function() {
    form.request({ parameters: { draft: true }});
  }
});

The problem might be that you want to make sure the form is saved as draft, but you don’t actually want to do the AJAX request.

new Test.Unit.Runner({
  setup: function() {
    // Create a form in the DOM and initialize an Autosave.
    $('sample').innerHTML = '<form action="" id="testform"><input type="checkbox" value="money" name="get" /></form>';
    
    this.autosave = new Autosave('testform');
  },
  
  teardown: function() {
    Moksi.revert();
  }
  
  testFormIsSavedAsDraftWhenChanged: function() { with(this) {
    // Stub Form.request on the form and record its requests
    Moksi.stub(this.autosave.form, 'request', function(options) {
      this.autosave.form.requests = this.autosave.form.requests || [];
      this.autosave.form.requests.push(options);
    }.bind(this));
    
    this.autosave.saveAsDraft();
    
    self.assertEqual(1, this.autosave.form.requests.length);
    
    var requestOptions = this.autosave.form.requests.last();
    self.assertNotNull(requestOptions.parameters);
    self.assertEqual(true, requestOptions.parameters.draft);
  }}
});

Notice the Moksi.revert() call? That makes sure all your stubs get reverted after running each test, which is really helpful if you only want to stub a function in one test and use the normally defined function in all the others.

3 comments

Adding development gem dependencies to a rails application.

Eloy Duran, 01 Dec 2008, 16:56 in ruby on rails and testing (edit).

It has been possible to specify gem dependencies for your application for a while. But sometimes when moving to another machine we would complain about there not being a development dependency option for config.gem as there is for gems. But we hadn’t actually given it any thought…

Today I had some weird test failures, which were related to not having test-spec 0.9 installed on my macbook. Most tests did pass because I did have an older version installed. So after a few minutes of thinking about it, I finally figured out the stupid simple solution. Simply define the dependencies in config/environments/test.rb!


# Development gems:
config.gem 'test-spec', :version => '0.9', :lib => 'test/spec'
config.gem 'mocha', :version => '0.9.3'

This one is probably obvious to some, but as far as I know it has never been actively promoted. And it’s just too easy to think of ;-)

3 comments

Testing randomness

Manfred Stienstra, 30 Sep 2008, 10:51 in ruby on rails and testing (edit).

The problem you run into when trying to write unit tests for methods using random data is that you can’t predict what random data it’s going to use. The idea behind a unit test is that you control the input and test whether the output is as you expected, so that’s a problem.

Let’s assume you have a token generator like this:

module Token
  DEFAULT_LENGTH = 8
  CHARACTERS = ('a'..'f').to_a + ('0'..'9').to_a
  
  def self.generate(options={})
    srand
    options[:length] = DEFAULT_LENGTH if options[:length].nil?
    (1..options[:length]).map { CHARACTERS.rand }.join
  end
end

Now you can write tests to see if it generates tokens of the correct length, doesn’t generate homogeneous tokens and doesn’t generate the same token a number of times in a row. Unfortunately these tests could succeed a number of times and suddenly fail for no apparent reason. Usually this happens when you’re on vacation and one of your collegues is running the test on some weird architecture leaving them baffled.

The solution is to make Array#rand predictable by stubbing out the normal implementation:

class Array
  def round_robin_rand_list=(list)
    @round_robin_rand_list = list
  end
  
  def rand
    current = @round_robin_rand_list.shift
    @round_robin_rand_list.push(current)
    current
  end
end

Now you can write a test that checks the output, because you control what the random method returns:

class TokenTest < Test::Unit::TestCase
  def test_token_generate
    Token::CHARACTERS.round_robin_rand_list = [0, 1, 14]
    assert_equal 'ab8ab8ab', Token.generate
    Token::CHARACTERS.round_robin_rand_list = [7, 12, 15, 3]
    assert_equal '169d169d', Token.generate
  end
end

Obviously you need to clean up after yourself in tests so you can’t just redefine methods. It would be really cool if we could do something like this:

class TokenTest < Test::Unit::TestCase
  def test_token_generate
    Token::CHARACTERS.define_round_robin_method(:rand, ['a', 'b', '8'])
    assert_equal 'ab8ab8ab', Token.generate
    Token::CHARACTERS.undefine_round_robin_method(:rand)
  end
end

We’ve implemented this as round_robin_method.rb, complete with its own testsuite. You could go even further and hook it into TestUnit to automatically undefine the round robin methods after a test has run. We’ll leave that as an exercise for another day.

2 comments

Aliasing dangerous methods in your tests

Manfred Stienstra, 21 Aug 2008, 11:29 in ruby on rails and testing (edit).

Lately we’ve been aliasing dangerous methods in our tests to make sure we don’t accidentally break something. It’s not very nice when tests delete your favorite episode of Sesame Street or add a MySQL user, so we had a lot of snippets like this laying around:


module Kernel
  mattr_accessor :allow_system
  self.allow_system = false

  alias original_system system

  def self.system(*args)
    if allow_system
      original_system(*args)
    else
      raise RuntimeError, "You're trying to do a system call, which is probably not a very good idea in a test."
    end
  end
end

We refactored it and now we specify the same thing like this:


ActiveResource::Connection.add_allow_switch :request
Kernel.add_allow_switch :system, :default => true

The code is currently included in on_test_spec, our plugin for writing Rails specs on test/spec.

No comments yet

Testing multiple calls to an object

Manfred Stienstra, 29 Apr 2008, 12:14 in testing (edit).

Have you ever wanted to test multiple writes to $stderr? Mocking frameworks aren’t good at that, instead you can write a simple class to record method calls.

class Receptor
  require 'singleton'
  include Singleton
  
  attr_accessor :messages
  
  def initialize
    @messages = []
  end
  
  def method_missing(*attrs)
    self.messages << attrs
  end
end

$stderr = Receptor.instance

And in your tests:

messages = Receptor.instance.messages
assert messages.include?(['puts', "[!] Error!"])
assert messages.include?(['puts', "[?] Couldn't find preferences file."])

1 comment

Quick Fix for acts_as_paranoid

Norbert Crombach, 17 Jul 2007, 08:26 in ruby on rails, testing, and broken (edit).

For those of you on Edge Rails, since changeset [7189] appears to have broken the current acts_as_paranoid we’ve been getting some test errors. There’s a quick patch I wrote available in this Pastie, but because scope_out is now recommended by Rick Olson himself this should only be seen as a migration path. Hopefully this will save you some trouble.

No comments yet

Code to Test Ratio Showdown

Manfred Stienstra, 03 May 2007, 17:40 in ruby on rails and testing (edit).

We had a little Code to Test Ratio showdown at the office today, probably because there was a woman present. It got me wondering what the Code to Test Ratio for other people is. So everybody, please post the CTTR of your current project in the comments.

Note: in Rails you can find your CTTR with rake stats.

25 comments

Testing with attachment_fu

Manfred Stienstra, 26 Apr 2007, 10:56 in ruby on rails and testing (edit).

When you’re testing uploads with attachment_fu, your files end up in RAILS_ROOT/public by default. This is not very handy because they might override your carefully uploaded bunny pictures in development. You can easily solve this by overriding the full_filename method on your attachment model. Let’s assume you have something like this.

class Asset < ActiveRecord::Base
  belongs_to :post  
  has_attachment :content_type => :image, :storage => :file_system
end

Then you can add the following to the file your tests are defined in:

class Asset
  def full_filename(thumbnail = nil)
    file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
    File.join(Dir::tmpdir(), file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
  end
end

This will make your files get written to /tmp/public. If you have multiple tests that have to override the attachment class, it’s probably best to put it in a separate file.

9 comments

Using OpenStruct as mock for ActiveRecord

Manfred Stienstra, 07 Mar 2007, 15:53 in ruby on rails and testing (edit).

As you may have noticed OpenStruct#id always returns the object id of the OpenStruct instance, even when you set id.

>> o = OpenStruct.new :id => 2
=> #<OpenStruct id=2>
>> o.id
(irb):4: warning: Object#id will be deprecated; use Object#object_id
=> 9850940

Fortunately there is a simple way around this.

OpenStruct.__send__(:define_method, :id) { @table[:id] }

Now OpenStruct behaves like we want.

>> o.id
=> 2

Note that hash and object_id still work fine. You probably want to keep in mind that we’ve redefined the default OpenStruct behaviour and it might cause problems elsewhere.

2 comments

Validating feeds in functional tests

Manfred Stienstra, 02 Feb 2006, 13:58 in ruby on rails and testing (edit).

In the past I usually tested the feeds a Rails application generated by writing a functional test that checked the HTTP status code and matched certain strings in the feed using a regular expression. If that checked out I hand-tested the feed using the online feedvalidator. Needless to say, this became very cumbersome after a few times, especially for one of our projects that generates a whole list of different feeds depending on the state of the account. Time to add validation to the functional tests.

Unfortunately the feedvalidator is (not yet) written in Ruby, so we have to do system calls to a python script to validate. I installed the feedvalidator in the script directory, which looked like the correct place to put this. A protected method on the testcase makes sure we can easily test feeds.

def validate_feed(content)
  validate = File.dirname(__FILE__) + '/../../script/feedvalidator/validate.py'
  path = Pathname.new(File.dirname(__FILE__) + '/../tmp')
  Tempfile.open('feed', path.cleanpath) do |tmpfile|
    tmpfile.write(content)
    tmpfile.flush
    result = `#{validate} #{tmpfile.path} A`
    unless result =~ /No errors or warnings/
      raise "Feed did not validate: #{result}"
    end
  end
end

So now the testcases looks like this:

def test_atom
  get :atom
  validate_feed @response.body
  assert_equal 'application/atom+xml', @response.headers['Content-Type']
end

Note that this validates the feeds on level ‘A’ which only tests for MUST directives. For completeness you should probably still test either with the online validator or temporarily set the validation to ‘AA’ or even ‘AAA’.

Rumor has it that if you validate your feeds at ‘AAA’ level Mark Pilgrim will come to your house and crown you Prins of the Feeds. Lucky lucky!

4 comments

Test your plugins!

Manfred Stienstra, 09 Dec 2005, 10:55 in ruby on rails and testing (edit).

Test are supposed to be run often, but they only get run if they’re easy to run. When you create a plugin in your Ruby on Rails project, it doens’t get run by default.

And that’s not very easy now, is it? Fortunately there is a standard task to run the plugin tests, which is called `test_plugins’. How do we get this task to run all the time?

RoR allows us to specify our own tasks in /lib/tasks, so we’re going to add a file called `test_plugins_by_default.rake’ to this directory, and we put the following in the file:

desc "Run test:plugins by default."
task :default do
  namespace 'test' do
    Rake::Task[:plugins].invoke
  end
end

Very well sir, but why does this work? The Rake User Guide tells us that a task that is declared multiple times, cumulatively adds it’s actions to the task. So ‘test_plugins’ will now be run in the default task.

No comments yet