Cookin' with Ruby on Rails - Integration Tests
Pages: 1, 2, 3, 4, 5, 6, 7

Boss: Hmmm... It seems like wasted effort to start by revisiting the home page. A link is a link is a link. It's going to take us to the same place every time the URL is used. Or does Rails include the infamous "branch on occassion" method? ;-)

Paul: I'm glad you said that, Boss. I was wondering why CB started all the high-level methods by invoking the browse_to_the_home_page method. Why'd you do that, CB? Seems to me like that was just like this; we'd already shown that the links were on the page. We could have just invoked them straight off and proved just as much couldn't we?

CB: From a purely technical perspective, you're absolutely right Paul. But Integration tests, or more precisely the development of the Integration tests, are about more than just technical adequacy. At least in my opinion. I think of their adequacy as best measured by the confidence the customer walks away with that the system will work the way they intend. Don't forget that, moving forward, I expect these tests to be constructed before the code gets written. And here, I want to make sure the customer is comfortable with how the home page works. When the customer does what Boss just did, says "we don't need to do that," it signals a good comfort level not just with the application, but with their understanding of the tools and the process we're going to use to test the application. And that's worth a few keystrokes! ;-)

Paul: Ahh... you clever devil you ;-)

CB: (grinning) OK, then. Let's write that new test. And actually, there's a real good reason to start the method with the browse_to_the_home_page method. It gives us a context for our test that will let us check the before state of the number of recipes on the page. It signals that we're testing the home page. We'll test the page where we create the recipe later. For now, we're still testing the behavior of the home page. Anyway, we already know from our Unit tests that we can add a recipe to the database. So now let's test that adding a recipe to the database actually adds it to the page. So...

def test_create_new_recipe_link
  recipes_on_page_before_create = count_recipes_before_create
  recipes_on_page_after_create = count_recipes_after_create
  verify_recipe_was_added_to_page(recipes_on_page_before_create, recipes_on_page_after_create)

And here are the new sub methods...

def count_recipes_before_create
  header_row = 1
  rows = css_select "tr"
  all_rows = rows.size
  recipe_row_count = all_rows - header_row
  return recipe_row_count

def click_the_create_new_recipe_link
  get "recipe/new"
  assert_response :success
  assert_template "recipe/new"

def create_new_recipe
  post "recipe/create", :recipe=>{:category_id=>1, :title=>"new_recipe"}

def count_recipes_after_create
  get "recipe/list"
  header_row = 1
  rows = css_select "tr"
  all_rows = rows.size
  recipe_row_count = all_rows - header_row
  return recipe_row_count   

def verify_recipe_was_added_to_page(recipes_on_page_before_create, recipes_on_page_after_create)
  assert_equal(recipes_on_page_after_create, recipes_on_page_before_create + 1)

The assert_select method allows us to get the count of specific elements on the page. It gives us an easy way to verify whether or not the number of elements is what we expected. But if we don't know how many of something are already on the page, then we need to use css_select. It returns an array of all the elements selected without making an assertion. Then we can use that array to, for example, feed an assertion. It gives us a way to make our tests less brittle. If we'd hardcoded the number of rows we expected and later added a recipe to our fixture, this test would have broken. Now it's more general and won't need to be changed just for that.

Also notice that when we create the new recipe here, we don't do a direct add to the database with and Recipe.sav like we did in our Unit tests. We use the Test Framework to send Rails just what it would see if the request came in from a browser like we did in our Functional tests.

OK, then. I think that covers the home page. We've validated the presence of all the content, including all the links we expect to find here, and we've validated the functionality of the links that we expect to return us here; the delete and create links. So, let's move on to the pages that the other links take us to. Since we just finished verifying its functionality as seen from the home page, why don't we make sure the page itself contains the elements it will need to have to do that? And any links to other that return it there or send the visitor to other pages we haven't seen yet too, of course. We should start at the highest level, like we did with the home page. You ready, Boss?

Boss: Sure, CB. The "Create a recipe" page? When I go to the "Create a recipe" page I want it to have the same title as the home page. And it should have boxes to enter the information. And a button to save it.

CB: Paul, would you mind browsing over to that page? If memory serves, I think there's another link on that page too.

create recipe page
Figure 26

Boss: Oh yeah. That's right. There's a Back link too. We'd better make sure we have a test for that too.

CB: I agree. The question we need to answer, though, is whether we treat it as a content test or a functionality test. In general, I like to treat the test structure recursively. Go to a page, check the content of the page, and check the functionality of the links or buttons on the page that, from a visitor perspective, leave us on the same page. Then, for each link or button on the page that takes us to another page, do the same thing. If that page has links or buttons that take us to pages we haven't already tested, follow them in a similar fashion.

So, for the Back link here, I think all we need to do is make sure the link is there and that it points to the right place. You OK with that approach, Boss?

Boss: That sounds good, CB. If you take that approach, I can see that you'll cover all the site's pages and functionality. And, it feels like it'll be easy to review too; it's a pattern. Just start at the home page and follow the pattern 'til you reach the end. I like it. And it means I can take off now. I've got a meeting to go to. Could you finish up with Paul? Later in the week he and I will go over the rest of the tests you two come up with and decide where we go from there. How's that sound to you?

CB: Sounds good, Boss. I think we've covered all the bases in terms of the statements and techniques we need right now. Paul and I will just do more of the same to flush out the rest of the site's tests. I'll look forward to hearing from you real soon to see what we do next.

Boss: Thanks, CB. With a little luck, it won't take me as long to get back to you as it did this time ;-)

Boss takes off for his next meeting, and CB and Paul finish writing the rest of the tests for their app as it exists today. Readers can download the cookbook2 app, including all the tests, here.

Articles in this series

Bill Walton is a software development/project management consultant/contractor.

Return to O'Reilly Ruby.