I was giving up on workflow tests when…

I’ve been growing a little disillusioned with my graphical workflow tests: implementing the test support code seems like a lot of work for little benefit. (It has nothing to do with the graphics—I’d have to write the same methods in a FitLibrary DoFixture or if I wrote the workflows directly in Ruby.)

So it was with a heavy sigh that I began to create this test:

User Registration Workflow

The first step started out as a bit of a puzzle. You see, I only have a couple of pages so far that require login. Links to those pages only display anywhere in the app when the user’s logged in. So I needed a plausible way for the user to head for one of those pages without clicking a link. It didn’t take much effort to come up with a suitable back story: Betty had navigated to the page where she could edit her profile, decided she’d rather jump there directly next time, and so bookmarked the page.

Having finished the drawing, I wrote the code for the first step:

  def tries_to_edit_her_profile_while_not_logged_in(test_name)
    focus_on(test_name)
    get_via_redirect edit_user_path(test_name)
    puts response.body
  end

As is my habit when first writing such a method, I ended it with a puts response.body. That way, I can sanity check that the method takes me where I want to go before implementing the next method in the test.

Well, it didn’t take me where I expected to go. Instead of being redirected to the login page, I’d been sent back to the home page with this error message:

    <div id="notice">
        Either you've found a bug or your devious plot has been foiled.
    </div> 

Where did that come from? Old tests. I’d already implemented the various controller methods behind a RESTian interface to the “users” resource. While I’d been enmeshed in that code, I’d asked myself “what if Aaron directly issues a request to edit Quentin’s profile?” I’d imagined that villainous man typing “www.wevouchfor.org/users/quentin;edit” in his browser’s location bar. There’s no legitimate reason for him to do that, so a stern warning is appropriate.

Here’s the test that makes me confident Aaron would be foiled:

  # Guard against manufactured URLs.

  def test_cannot_update_a_user_other_than_self
    new_profile = NewProfile.for(’quentin‘).is_entirely_different_than(users(:quentin))

    put :update,
        {:id => users(:quentin).login, :user => new_profile.contents},
        {:user => users(:aaron).id}
    assert_redirected_to(home_path)
    assert_hackery_notice_delivered
    new_profile.assert_not_installed
  end

Despite what I’ve written about more detailed tests making you think harder about each case, I then blew it. When I moved from thinking about logged-in Aaron trying to edit Quentin to thinking about a not-logged-in user doing the same thing, I jumped immediately to the conclusion that the latter attempt was equally illegitimate. So I copied, pasted, and tweaked to create this test:

  def test_cannot_update_a_user_unless_logged_in
    new_profile = NewProfile.for(’quentin‘).is_entirely_different_than(users(:quentin))

    put :update,
        {:id => users(:quentin).login, :user => new_profile.contents}
        # Nothing in session
    assert_redirected_to(home_path)
    assert_hackery_notice_delivered
    new_profile.assert_not_installed
  end

The code that makes this test pass is the cause of the surprising redirect in the workflow test.

It was only when I stepped back from these Rails “functional” tests (which are about sending in a single HTTP request and seeing what happens) to think about a user workflow that I realized the not-logged-in case could be legitimate. The workflow is plausible, so the older functional test is wrong: the redirection should be to the login page, not to the home page with an error.

Chalk one up for workflow tests.

Now, it’s entirely possible that a different sequence of events wouldn’t have led me to stumble over this bug. That’s OK. I’m not proud—I’m happy to accept help from dumb luck.

(You could make the fair criticism that I should have written the workflow test first. That might have saved me from going down the wrong path. However, I’m a novice Rails programmer who just came back from a month spent forgetting what Rails I knew, I’m even newer at REST, and I don’t learn well while multitasking (which I’ve had to do continuously for much of the last 17 years). So I was not experiencing a great deal of ease as it was. Adding the struggle of building onto a fledgling business-facing test library at the same time was too much for my patience. So I put off the workflow test.

(Do other people find that test-driving your first app in a new framework makes learning significantly more frustrating?)

One Response to “I was giving up on workflow tests when…”

  1. Chris McMahon Says:

    I write a lot of workflow tests, but almost always pointed at some sort of interface behind which is a black box. I don’t think it’s dumb luck at all– I think tests like these, that visit more than one place, that test a path through an application for some definite purpose– have as a feature that they expose issues exactly like the one you found.

    Instead of the laser-like focus of unit tests, I think of these workflow tests as a kind of cargo net. When you throw a lot of stuff in the back of your truck, you want a cargo net that stretches from front to back, side to side, and touches the cargo at a lot of points. A cargo net is valuable precisely because you can never be sure what’s going to come loose, and you want to be in a position to catch whatever bounces out when you hit a bump.

Leave a Reply

You must be logged in to post a comment.