David Chelimsky

random thoughtlessness

expectations and assertions, together again at last

Unlike rspec core, ‘spec/rails’, a.k.a. rspec_on_rails, wraps ‘test/unit’ in order to take advantage of facilities that are tightly coupled to ‘test/unit’ (e.g. fixtures).

One thing that’s been missing, however, has been the ability to just use ‘test/unit’ assertions right in your specs. With the upcoming 0.8 release (anything after rev 1451 if you’re a trunkster), you’ll now be able to do so.

This means that, in theory, you’ll have access to all of the plugins that are available that are bound to ‘test/unit’, like form_test_helper and the hpricot_test_extension.

I say “in theory” because it is impossible for us to commit to support all of the existing plugins any more than any plugin developer can commit to supporting compatibility with all other plugins. That said, I’ve tried both of the plugins mentioned above and they “just worked”.

view specs are about to get a whole lot easier

The next release of rspec_on_rails will include a complete port of assert_select. So now you’ll be able to spec your login form like this:

<code>
context "login/login" do
setup do
  render 'login/login.rhtml'
end

specify "should display login form" do
  response.should have_tag("form[action=/login]") {
    with_tag("input[type=text][name=email]")
    with_tag("input[type=password][name=password]")
    with_tag("input[type=submit][value=Login]")
  }
end
end

`

How sweet is that!!!

Trunk-sters out there can do this right now. The rest can expect a release within the next couple of weeks.

clean out init.rb in rspec_on_rails plugin

There is a conflict between spec/rails and test/unit that prevents you from running tests if specs are loaded up. Previous versions of spec/rails had require statements in init.rb in the plugin’s root directory. This meant that you were ALWAYS loading rspec, even in your dev and production environments.

The current version (0.7.5) relies on ~/spec/spec_helper.rb to require ‘spec/rails’, and init.rb is empty.

If you are working with any previous versions, be sure to clean out your ~/vendor/plugins/rspec_on_rails/init.rb (some versions had that directory as rspec).

Fighting the Urge to Ask

ActiveRecord provides a lot of magic methods that let us get at the properties of a given AR subclass. This is absolutely fantastic news!

Except for one thing.

Cohesive Models

Now that Rails uses Ruby to express database structure (migrations), it seems silly to me that we can’t just express that right in the model classes. Something like this:

1
2
3
4
5
6
class Person < ActiveRecord::Base
  fields do |f|
    f.add :first_name, :string
    f.add :last_name, :string
  end
end

Doing this would not only make models more cohesive, but it would make it easier to specify a lot about models without ever touching the database.

We’d need a means of discovering necessary migrations on the fly, and that could get very tricky very quickly, but I think it’s worth exploring.

tutorial - rspec: stubs and mocks

RSpec’s mocking/stubbing facilities are enhanced significantly in RSpec-0.7.

You can now intermingle stub methods and mock expectations on the same objects. This applies to instances of Spec::Mocks::Mock or ANY other object or class.

Having integrated stubs and mocks available not only supports isolated and fast controller specs, it also provides a nice way to separate setup noise from the “interesting bits” in your specs. I try to exploit this by preferring stub methods (stub!) in my setup and mock expectations (should_receive) in specify blocks.

1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt><strong>5</strong><tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt><strong>15</strong><tt>
</tt>16<tt>
</tt>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt><strong>25</strong><tt>
</tt>








<tt>
</tt>context <span class="s"><span class="dl">"</span><span class="k">the PersonController</span><span class="dl">"</span></span> <span class="r">do</span><tt>
</tt>  controller_name <span class="sy">:person</span><tt>
</tt>  <tt>
</tt>  setup <span class="r">do</span><tt>
</tt>    <span class="iv">@person</span> = mock(<span class="s"><span class="dl">"</span><span class="k">person</span><span class="dl">"</span></span>)<tt>
</tt>    <span class="co">Person</span>.stub!(<span class="sy">:new</span>).and_return(<span class="iv">@person</span>)<tt>
</tt>  <span class="r">end</span><tt>
</tt>  <tt>
</tt>  specify <span class="s"><span class="dl">"</span><span class="k">should create a new person on GET to create</span><span class="dl">"</span></span> <span class="r">do</span><tt>
</tt>    <span class="co">Person</span>.should_receive(<span class="sy">:new</span>).and_return(<span class="iv">@person</span>)<tt>
</tt>    get <span class="s"><span class="dl">'</span><span class="k">create</span><span class="dl">'</span></span><tt>
</tt>  <span class="r">end</span><tt>
</tt>  <tt>
</tt>  specify <span class="s"><span class="dl">"</span><span class="k">should assign new person to template on GET to create</span><span class="dl">"</span></span> <span class="r">do</span><tt>
</tt>    get <span class="s"><span class="dl">'</span><span class="k">create</span><span class="dl">'</span></span><tt>
</tt>    assigns[<span class="sy">:person</span>].should_be <span class="iv">@person</span><tt>
</tt>  <span class="r">end</span><tt>
</tt>  <tt>
</tt>  specify <span class="s"><span class="dl">"</span><span class="k">should render 'person/create' on GET to create</span><span class="dl">"</span></span> <span class="r">do</span><tt>
</tt>    controller.should_render <span class="sy">:template</span> => <span class="s"><span class="dl">"</span><span class="k">person/create</span><span class="dl">"</span></span><tt>
</tt>    get <span class="s"><span class="dl">'</span><span class="k">create</span><span class="dl">'</span></span><tt>
</tt>  <span class="r">end</span><tt>
</tt>  <tt>
</tt><span class="r">end</span><tt>
</tt>

In this example, the line …

<code>
Person.stub!(:new).and_return(@person)
</code>

… in setup stubs the method so GET ‘create’ will work every time. The line …

<code>
Person.should_receive(:new).and_return(@person)
</code>

… in the specify block “should create a new person on GET to create” sets an expectation that must be met. Even though it seems like duplication of the stub method, it is serving a different function: it tells the story for that spec.

Note how in the other specs there is no focus on Person.new. It’s out of the way, but the stub in the setup makes sure its taken care of.

So use stubs to take care of the “noise” (in this case stuff that has to be there to get the specs to run), and use mock expectations to put focus on the fact that something is expected.

BDD - specs and tests, together again at last

In discussing the 0.7 release of ‘spec/rails’, I find myself talking about how the model, view, controller and helper specs should be accompanied by integration tests. Those are the words that are emerging naturally. Admittedly, integration ‘specs’ just sounds silly to me!

This strikes me as interesting because we’ve been talking about wanting to eliminate the ‘test’ word from our BDD vocabulary, but as Dan North has pointed out, it keeps coming back.

What’s emerging for me, however, is that it comes back in a different form. Perhaps the little bits we write in the TDD/BDD eeny-weeny red-green-refactor cycle are fundamentally different animals

But an evermore clear distinction emerges between the design activity of BDD in the small vs the customer collaboration activity of BDD in the large. (OK, medium to some of you)

In other words I think it’s OK to use the word test when we talk about integration, system, whatever-higher-level tests. And it’s OK to talk about specs when we talk about the details.

tutorial - driving view behaviour with ‘spec/rails’

[Updated for 0.9 on 2007/04/12]

rspec-0.9’s ‘spec/rails’ (RSpec on Rails) plugin lets you spec your views in complete isolation of any controllers. This means that you can spec your views even before there are any models or controllers! Once models and controllers do exist, changes to them will not cause your view specs to fail.

Here’s a brief tutorial to get you started.