Auto-loaded fixtures for RSpec: introduction

Posted by oandreev on March 27, 2007

Have you ever forgotten to setup required fixtures in spec because of their quantity? Sometimes we have up to 10..20 tables to be filled before spec runs. In opposite, you may write many tiny contexts, so that you need short fixtures definitions in each of them. But the latter leads to very unmaintainable un-DRY code, especially when you add new entity to the project which touches many specs.

One may consider make all fixtures global, so that you don’t need to use fixtures method at all. But having 500 specify blocks and loading 30 tables per each isn’t that fast.

What we need is auto-loaded fixtures. Once model Person is mentioned in specify block, fixture people is loaded. That strategy guarantees three things:

1. Models always get required data in database
2. We don’t spend time on loading non-used fixtures
3. We don’t have to manage one more dependency, which is not that cool at all.

The main difficulty here is how catch the moment when model class appers in specify do .. end. Each constant gets catched and autoloaded by active_support/dependencies, but it is done only once, so it is not the right place to fixturize :) database.

One of the possible solutions is to hide ActiveRecord model constants before specify so to catch them in const_missing. Constants are collected within AR::inherited and loaded or revealed in const_missing.

The main problem now is to figure out which evaluation context asks for constant. This is needed for calling fixtures in the right place. You may ask: why don’t you just add const_missing to every context object to catch constant exactly where fixture is to be loaded. I tried that. RSpec uses blocks for specs, and ruby blocks, as everybody knows, are pure closures. Constant mentioned in block context have no way to be searched for in block caller context. Even using instance_eval or eval with binding (that’s nearly the same as instance_eval in that case).

Another problem is to catch fixture shortcuts: like people(:oleganza). Person class may not be even loaded before such call, so we need to do some simple heuristics to load class and fixtures altogether.

Of course, instantiated fixtures (@people) are not supported. They are slow and nobody uses them anyway.

I’m still in search for working solution. Hope it will be nice. Stay tuned, more to come.

JRuby: Unicode out-of-box

Posted by yrashk on March 06, 2007

I’ve started playing with JRuby 0.9.8. I personally think that it has great potential for web development in Rails (they have 98% of rails tests passing already !).

What I was pleased to see is that (thanks to Java) unicode is working in JRuby just fine without any workarounds like String#chars :

That’s very, very good! Continuing to explore JRuby possibilities.

RSpec Generated Spec Name for "be"

Posted by yrashk on March 02, 2007

I was talking about generated spec names few hours ago. Although, I’ve found that they simply don’t work properly with “be” matchers. I’ve tried to produce a quick workaround for this. That’s what I’ve created:

 
gem 'activesupport'
require 'active_support'

class Spec::Matchers::Be
  def description
     "be #{@comparison}#{@expected} #{@args.to_sentence}" 
  end
end
 

Now I can run following code:

 
context "Random number in 0..100 range" do 

  setup do
    @random_number = rand(100)
  end

  specify do
    @random_number.should be < 100
  end

  specify do
    @random_number.should be >= 0
  end

  specify do
    @random_number.should_not be_nil
  end

  specify do
    @random_number.should be_between(0,100)
  end

  specify do
    @random_number.should_not be_between(100,200)
  end

end
 

and get this as output:

Random number in 0..100 range
- should be < 100 
- should be >= 0 
- should not be nil 
- should be between 0 and 100
- should not be between 100 and 200

Update: This improvement was included into rspec trunk

RSpec Simple Specifications

Posted by yrashk on March 02, 2007

What I also like in RSpec is that it constructs a name for simplistics specifications, like:

 
 context "Random number in 0..100 range" do 

  setup do
    @random_number = rand(100)
  end

  specify do
    @random_number.should < 100
  end

  specify do
    @random_number.should >= 0
  end

  specify do
    @random_number.should_not be_nil
  end

end

Random number in 0..100 range
- should < 100
- should >= 0
- should not be nil

RSpec 0.8.1 is out. Out of any expectations!

Posted by mklishin on March 02, 2007
Ruby developers beloved BDD framework RSpec goes 0.8.1. This is actually a bugfix release after 0.8 one day earlier. It has new expectation syntax although old one is here to stay up to 0.9. But what is much more interesting, now we have a way to add our own expectations and matchers following simple methods overriding rules.

class BeInZone
   def initialize(expected)
      @expected = expected
   end
   def matches?(actual)
      @actual = actual
      bob.current_zone.eql?(Zone.new(@expected))
   end
   def failure_message
      "expected #{@actual.inspect} to be in Zone #{@expected}"
   end
   def negative_failure_message
      "expected #{@actual.inspect} not to be in Zone #{@expected}"
   end
end

def be_in_zone(expected)
    BeInZone.new(expected)
end

and use it like this:

module CustomGameMatchers
    class BeInZone
      ...
    end

    def be_in_zone(expected)
      ...
    end
end

context "Player behaviour" do
    include CustomGameMatchers
    ...
end

Given Ruby DSL power this may lead to some unpredictable addiction to BDD and Ruby in general. Don't forget to run script/generate rspec as you're done with installation of Rails plugin from tag 0.8.1 cause it generates one more file for run preferences. I said preferences? Yes, like colored output. Passed specs are now green in Bash, messages are purple and failures are red. Really easy to recognize after months of black and white. The same day Mauricio Fernandez posted announcement of RCov 0.8, code coverage tool for Ruby that works with RSpec. Not that bad... And don't forget to read new documentation to RSpec, a lot of stuff changed there but everything is still easy to learn and specs look even closer to human written requirements text. What else could we ask BDD framework of?