[Rspec-devel] BDD with Rails, deciding on context boundaries

aslak hellesoy aslak.hellesoy at gmail.com
Tue Aug 15 11:09:47 EDT 2006


On 8/15/06, Luke Redpath <luke at agileevolved.com> wrote:
> Hi Everybody
>
> I've been working with RSpec on my latest project and I'm currently
> writing a 5 part article about testing Rails apps properly and I plan
> on mentioning BDD and RSpec. First of all though, I want to clear up
> some of my own confusion. I believe *may* have been focussing on
> testing state in my specs rather than behaviour and I'd like advice
> on how to improve what I'm doing if I am indeed going in the wrong
> direction.
>

I'm going to suggest some categories to help distinguish between an
object's state and behaviour. (It would be nice to get help to nail
these - I'd like to have it in RSpec's docs)

= State =
* Instance variables (attr_reader)
* Methods that return values without
** modifying internal state
** invoking methods on other objects that may modifying their state
* Methods that modify internal state

= Behaviour =
* Methods that invoke methods on other objects

> Let me focus on model tests for the moment. Lets take a User model. I
> find that the first few contexts for an ActiveRecord model in Rails
> tend to be the same, generally a "New foo" and a "Saved foo". I try
> to avoid testing built-in AR features such as find/destroy/create as
> this should be covered by AR's own tests. Here's some specs for a
> User model on my current project:
>
> http://svn.lazyatom.com/public/plugin_repository/branches/
> community_features/spec/models/user_spec.rb
>
> Does this look like the right approach to take? If not how could
> these specs be better - more BDD-like? Instead of this:
>
> context "New user" do
>    specify "should not be valid" do
>      @user.should_not_be_valid
>    end
> end
>
> Should I be doing something more like this:
>
> context "New user" do
>    specify "should return false when calling valid" do
>      @user.should_not_be_valid
>    end
> end
>

As you're experiencing, the context/specify (CS) style allows for a
lot of different styles (as do xUnit). Since we're still stuck with CS
I think either is fine. Which style is more appropriate is a judgement
call on how complex/intricate your case is. In this case it's rather
simple so I'd prefer the first.

One of the goals of the upcoming? GWT format is to reduce confusion
about format as you describe here. In GWT it might look something
like:

given "A new user" do
  setup do
    @user = User.new
  end

  when "valid is invoked" # When is like a comment, but it would
appear in specdocs
  then "the result is true"
    @user.should_be_valid
  end
end

> I guess the main difference between the two is the first looks more
> like a user story whereas the second looks more like an explicit test
> of the code's behaviour (rather than the behaviour of the conceptual
> model itself). Which is more correct?
>

Like I said above, either is fine to me. Once we get a richer BDD API
you'll have less choices.

Now, regarding whether you're verifying state rather than behaviour.
In this example we're definitely verifying state, not behaviour.
ActiveRecord (as most ORMs) are inherently very state-centric. And the
state centric aspects of AR is - as you mention - tested in AR's own
test suite.

This doesn't mean AR objects can't exibhit additional behaviour
defined by you. I very often find myself refactoring behaviour defined
in controllers into the AR classes themselves. One example would be:

given a saved user with email # known initial state
when the password is changed # event
then a password notification email is sent to the user # expected behaviour

> The next thing I want to discuss is controller specs. First of all, I
> think a Rails controller is far too wide in scope to focus on as a
> whole - its always irked me that Rails' unit test stubs for
> controllers (*ahem* sorry *functional* tests if we believe Rails'
> terminology)

Agree - it's not a 'functional' spec in my book either.

> generate a stub testcase for the entire controller. I
> instead prefer to concentrate on the individual actions.
>
> So lets take, for example, a UserController#create action. Now how
> can we break this down into contexts? One way of looking at
> controller actions is a conceptual "object" that you send "messages"
> to, like normal objects, except in this case the "object" is really
> just a controller method and the "messages" are HTTP Verbs and
> parameters. So would we split the contexts up by verb?

I'm torn here. I *really* want my GWT now! :-)

Since controllers represent the user interface (to some degree) I
would try to write them in a way that could be understood by a user.
Ideally you'd work with the user to write the specs. Example:

given I am on the user signup screen
when I submit an unused user name and password
then I will get confirmation that a new account is created

The verb+params map to a method (event) so I think this is more
related to the 'when'.
But maybe this is more appropriate for the 'integration' specs.

> But what about
> different results - a create could be successful or it could fail.
> Where does this fit into things? One way of breaking it up would be
> like this
>
> User creation via GET
> - should never create a user
> - should flash an "invalid request" message
> - should redirect back to the previous page
>
> Successful user creation via POST
> - should create a new user
> - should flash a success message
> - should redirect to the user list page
>
> Failed user creation via POST
> - should not create a new user
> - should flash an error message
> - should redirect back to the new user page
>

This is how I currently tend to break things down. It does express a
lot of the system's behaviour. The main problem is that it doesn't
necessarily make sense to a non-technical user. Another problem is
that it's somewhat in conflict with what we're trying to achieve with
contexts or 'given' - break things up by initial state.

But in the current incarnation of RSpec I think the way you're doing it is fine.

> Would this be how you would break it down? Also, do you believe these
> specs are the place to specifiy the behaviour of the resulting view
> as well as the action behaviour? Or should the view behaviours have
> their own specs entirely?

If you put on the user hat I think it makes sense to verify the
controller and the view together. Example:

Given I am not logged in # Initial state
When I try to navigate to the user admin section # Controller - invoke
controller method
Then I am told to log in # View - verify the HTML

Now - we're not really verifying the behaviour of the view here -
we're verifying the behaviour of the system - at a higher granularity.
We're just using the view to verify the expected behaviour of the
*system*. Which IMO is pragmatic and fine.

> Or is test::unit and rspec the wrong place
> to try and test your view output - would an acceptance testing tool
> like selenium or watir be more suited to the task?
>

They are not exclusive - they are yin and yang! You can use RSpec (and
Test::Unit for that matter) with both Watir and Selenium remote
control. Here is one of my RSpec/Selenium RC specs (I'm writing an
XP/Scrum story/iteration/planning tool):

context "A running iteration" do
  setup do
    @sel = Selenium::SeleneseInterpreter.new("localhost", 4444,
"*firefox", "http://localhost:3000", 5000)
    @sel.start
  end

  specify "should allow finishing and tag all stages and stories" do
    @sel.open_and_wait "http://localhost:3000/iterations/1"
    # TODO: verify that the iteration is still 'running' (visual clue)
    @sel.click_and_wait "link=Iteration Status"
    @sel.click_and_wait "link=Finish this iteration"
    # TODO: verify that the iteration is 'finished' (visual clue)
  end

  teardown do
    @sel.stop
  end
end

You can do similar things with Watir - it's just a library that lets
you talk to the browser. Stick it in your specs!

I think Watir/Selenium serve the same purpose as Rails integration
tests. I'd use either of them for high level stuff. Which one is more
appropriate is more a question of which one is more suited for you
*technically*.

> Finally, what are your thoughts on simulating the above situations
> like successful/failed creations by stubbing out ActiveRecord methods
> like save() (the Stubba library that comes with Mocha -
> mocha.rubyforge.org - is particularly good at doing this)? I feel
> that by simulating successful/failed requests by passing the
> appropriate parameters, you are coupling your controller tests to
> your model's implementation - i.e. if what makes a model valid
> changes, you also need to change your controller tests, which seems
> like a bad smell to me.
>

Word!

I think that controller 'unit' specs should ideally talk to mock
models. Stubba seems great for this although I haven't tried it. I
know David is experimenting with some similar stuff on on of the RSpec
branches too.

> What would be really nice is to use mocks so that your model *and*
> controller tests never touch the database. Believe it or not,
> ActiveRecord doesn't make this easy - ActiveRecord barfs if you send
> it a mock when it expects another ActiveRecord object - it explicitly
> type checks. So much for duck typing!
>

Controllers should definitely be verifiable without having to talk to
the real models (using RSpec's mocks or Stubba or something).

When it comes to AR - I agree it's a pain to try to divorce it from
the database, but I don't see this as as much of a limitation
(although sometimes it is).

I'm still doing a fair amount of Java using JMock. In my early days of
mocking I tried to mock everything - including the JDBC and servlet
APIs. Until Joe Walnes taught me "only mock your own APIs".

ActiveRecord is where the rubber (your code) hits the road (the
database). If you have model level logic that you want to verify
without a database, consider implementing this in vanilla POROs - not
AR classes. In a running system they would talk to AR models, but yoy
could mock the AR classes (they are your own) when you verify these
'non-persistent' models' behaviour (trying hard to avoid the T word
here hehe).

Of course this has a tradeoff of introducing extra layers - diverging
from the AR pattern and 'behaviour goes with data' OO principles. But
in some cases it makes sense.

> Thanks for listening to my semi-coherent ramblings!
>

Likewise!

Cheers,
Aslak

> Cheers
> Luke Redpath
> _______________________________________________
> Rspec-devel mailing list
> Rspec-devel at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-devel
>


More information about the Rspec-devel mailing list