Close and Go BackBack to Viget

Announcing spect, a helping library for Test::Unit

Clinton R. Nixon
Clinton R. Nixon, Former Staffer, May 22, 2008

We've tried out a lot of test frameworks for Ruby here at the Lab. I have been a big fan of test/spec in the past, and like shoulda's nested contexts. test/spec's wrapped assertions - like user.should.not.be.nil instead of assert_not_nil user - are a particular feature I dig.

I wanted to use these with shoulda, and started to extend shoulda to have its own set of wrapped assertions, but when I looked in the test/spec code, I saw this beauty:

module Test::Spec
  class Should
    ...
    def add_assertion
      $TEST_SPEC_TESTCASE && $TEST_SPEC_TESTCASE.__send__(:add_assertion)
    end
    ...
  end

  class TestCase
  ...
    module InstanceMethods
      def setup                 # :nodoc:
        $TEST_SPEC_TESTCASE = self
        super
        call_methods_including_parents(:setups)
      end
    ...
    end
  ...
  end
end

I don't want to put down a piece of code I've used a lot and enjoyed, and I think this is a practical way for test/spec to handle increasing the assertion count for its wrapped assertions, since they all start with a method on Object. I, however, couldn't bring myself to use a global variable to handle this task. I thought for a while, and after being inspired by JDave, I decided to start with a method on Test::Unit::TestCase.

Announcing spect

I released my test library today as a Ruby gem called spect. It's very simple to use. Once you require spect, you can use the method expect in Test::Unit::TestCase or any derived classes, like a shoulda should block or a test/spec specify block. expect sets up expectations like the following:

expect(user).responds_to admin?
expect(user).is.an.admin
expect(user.name).to.not.match /mr_jenkins/
expect(user).to.be.equal User.find(:first)
expect(user).is.not.not.not.not user
expect(ZeroDivisionError).raised_by do
  user.id / 0
end

Most of the Test::Unit assertions are wrapped, with the following mappings:

  • assert_equal is equal
  • assert_in_delta is close_to
  • assert_match is match
  • assert_nil is nil
  • assert_respond_to is respond_to
  • assert_same is same
  • assert_raise is raised_by
  • assert_throws is thrown_by

In addition, all predicates on an object can be tested for truth by calling them per normal (admin?) or by dropping the question mark (admin.)

There are several methods used solely for grammatical purposes. to, an, and a are no-ops and just return self. is and be are no-ops unless they are passed an argument, in which case they assert equality between the original object and this argument. not reverses assertions, but also can be passed an argument to assert inequality.

I am sure there are a lot of improvements to make. I thought of one writing this blog post (equal_to should be an alias for equal.) I invite you to check out the code at our GitHub repository and submit patches.

blog comments powered by Disqus

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


How many days in a non-leap year?

Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.