[rspec-users] State Based vs. Behaviour Based Spec/Testing

Pat Maddox pergesu at gmail.com
Sun Mar 25 02:39:38 EDT 2007

I actually wrote something in our company wiki tonight that touches on
this.  I'm going to be lazy and just copy and paste it here (it's
late).  Anyway to summarize my thinking on the state-based vs
interaction-based testing, it's...

Some of your code's job is to tell a story
some of your code's job is to get stuff done

The first part is pretty easy.  You just write your mock expecations
and go.  There's not a whole lot of state-based testing.  The second
part is more of a mix of the two, depending on what an object or
method should do.  When a method has to interact with other objects,
obviously interaction-based testing is looking like a good option.  If
you're interested in a method's return value, or whether it changes
the object's state, you have to do some state-based testing.

Here's the wiki excerpt I wrote up.  Hope you find it helpful.



We should have good enough domain model code that our controllers tell
an easy to read story. (PM: yes I'm aware I sorta suck at that). They
really shouldn't have any business logic, that's all done in the
models. The controller just coordinates it all and exposes data to the

With that in mind, the vast majority of your controller specs should
be behavioral expectations. We just want to know that the right method
was called on the right object. Really the only state-based
verification you'd do is to ensure that certain instance variables are
correctly set for the view. I'll show an example for requesting
/video_formats/1.xml, which is the XML representation of the
VideoFormat with ID=1. We are interested in verifying three things:

    * The proper video format is selected from the db
    * We call that video format's #to_xml method
    * The result of #to_xml is rendered as the page output

context "Requesting /video_formats/1.xml using GET" do
  controller_name :video_formats

  setup do
    @mock_format = mock("format")
    @mock_format.stub!(:to_xml).and_return "XML"
    VideoFormat.stub!(:find).and_return @mock_format

  def do_get
    @request.env["HTTP_ACCEPT"] = "application/xml"
    get :show, :id => "1"

  specify "should be successful" do

  specify "should find the VideoFormat" do
    VideoFormat.should_receive(:find).with("1").and_return @mock_format

  specify "should render the format as XML" do
    @mock_format.should_receive(:to_xml).and_return "XML"
    response.body.should_eql "XML"

The specification clearly communicates what should happen when we
request /video_formats/1.xml. In general I like to keep only one
expectation per specification, but in the last one ("should render the
format as XML") it makes sense to combine the two expectations since
they're closely related.

Now I'm going to show an example of a spec that is a good idea and an
important spec, but probably in the wrong place.

context "Requesting real /video_formats/1.xml using GET" do
  controller_name :video_formats

  setup do
    @format = VideoFormat.new
    VideoFormat.stub!(:find).and_return @format

  def do_get
    @request.env["HTTP_ACCEPT"] = "application/xml"
    get :show, :id => "1"

  specify "use real video format xml and make sure id is set" do
    response = do_get
    data = response.body
    # check the feed to make sure the format id is in there, we need
it for the databurst addAsset call
    h = Hash.from_xml data
    format = h['video_format']
    assert format.has_key?('id')

The problem with this spec is that it's not verifying the behavior of
the VideoFormatsController#show action, but rather verifying the
behavior of a VideoFormat object itself. Because we know from the
previous spec that #show renders the VideoFormat's XML representation
as a response, by finding the <id> property in the XML we implicitly
know that a VideoFormat renders its id attribute.

A better approach would be to move this into the VideoFormat model
spec. That way the knowledge of a VideoFormat's behavior is located in
one place, and our controller specification can drop its dependence on
the database and go back to simply telling the action's story.

context "A saved VideoFormat in general" do
  setup do
    @format = VideoFormat.create

  specify "should include the ID in its XML" do
    Hash.from_xml(@format.to_xml)["video_format"]["id"].should_not be_nil

Now we've explicitly stated that a VideoFormat should include <id>
when it renders itself as XML.

We can remove the second controller specification, because we've now
verified that (a) /video_formats/1.xml calls the VideoFormat's #to_xml
method, and (b) calling #to_xml on a VideoFormat includes the id

More information about the rspec-users mailing list