Cookin' with Ruby on Rails - More Designing for Testability
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Paul: I'm pretty comfortable with it, I think. Let's take a look at another one.

CB: Sure. Let's take a look at the new and create methods together since we'd normally expect to see them occur in sequence.

the controller, test, and setup for the index method
Figure 17

CB: In the new method in the controller, we create a new instance of the category class. That instance variable gets passed to the default view which; in this case, is new.rhtml. To test this, our test_new method uses the request object created by the setup method to send a GET request to the new method in our category controller. Then, as in the test_list test method we just looked at, the test framework looks at the response that Rails creates. It checks to make sure the status code is set to one of the Success codes and to make sure that the correct template was used to create the HTML page that's being returned. The next line in our test method is one we didn't see in test_list.

assert_not_nil assigns(:category)

This line tests to make sure that the new method in the controller actually created an instance variable named @category as expected.

Paul: Seems like it should say something like...

assert_assigns @category

CB: From a readability perspective, that's not a bad idea at all. Especially if we were going to be reviewing this with someone like Boss. In fact, since I've already showed you how easy it is to create custom assertions, I'd say that'd make a great homework assignment! ;-) When you do it, you'll need to know that assigns is a hash, one of four that gets created by the test framework for every response it gets from Rails. We'll cover the others as we get to them. For now, it'll do to know that :category is a key in the assigns hash, that a key-value pair will be created in that hash for each instance variable created in the method, and that the values of the instance variables will be accessible via standard Rails dot notation. As it stands, our test is only checking to see that a variable has been created by the controller method. In just a minute, we'll see how to check to make sure its values have been assigned as expected. But for now, we can see that we're testing for everything we asked Rails to do in the new method.

So now let's look at the test_create method. The first and last lines should look familiar. We used them in our Unit tests to make sure that a record had actually been saved to the database. That's exactly what they're doing here. The second line in the method is creating a request, this time a POST request, and passing in a hash. In normal operation, this is the value that would be passed to Rails via the params hash when a client submits a request. Take a look at the create method in the controller. The first line is...

@category =[:category])

The hash in the second line of the test_create method...

:category => {:name => "new category"}

creates the key-value pair that's put in the request object and passed in params[:category] to the create method in the controller.

The next two lines in the test method verify that the status code Rails is sending "back to the browser" is a redirect, and that it's redirecting to the list method in the same controller.

CB: You OK with that?

Paul: Yeah. I think I'm following you.

CB: Cool. So... what's missing?

Paul: The test method is only testing the success path. The controller method has two paths; one for a successful save and a different one for an unsuccessful save. Our test method needs work.

CB: Would you like to lead? Or shall I?

Paul: Allow me. Please ;-)

I guess the first thing I need to decide is whether to put this inside the test_create method or to write a new method. If I put it inside the test_create method, I'll save some test execution cycles since the setup method won't have to run again for a new method. But if I do that, then anybody reading the tests will have to dig a little harder to see that I'm testing for failure too. What do you think, CB?

CB: Well, I tend toward making it as easy as possible to grok the functionality of the app from the test cases. But in this case, I think we could accomplish both goals if we put the test inside the existing method, but rename the method so that a reader would get what's being tested just from the name. How 'bout maybe renaming test_create to test_create_success_and_failure ?

Paul: I like it. OK. So I'll add some code to the existing test method. Let's take another look at the create method in the category controller and the test_create method in the test case.

create and test_create methods
Figure 18

Paul: Well, the most obvious difference between a successful create and a failed create is that, on a successful create the number of records in the table will increase. That's a "Duh" and the scaffolded test code includes that test. It doesn't test to make sure the number doesn't increase on a failed create though. And looking at the controller method again, it looks like there's something that happening on a successful create that isn't being tested yet. What's the flash[:notice] line do? And can we test it?

CB: The flash object is sort of a general purpose way to send messages back to the browser. The most common, and many would say most appropriate, use of the flash object is to send back success messages like this. If a save fails, for example, the validation automatically adds a message to the errors object and, typically, in the associated view file, we'll have a line that renders the messages with error_messages_for 'some_specific_object'. The use of the flash object, on the other hand, is common enough that the scaffolding automatically puts the line to render it in the application layout file, app\views\layouts\application.rhtml, so that it's available for every page we render. And, yes, we can test for it. Remember just a minute ago I said that assigns was one of four hashes constructed for every response? Well, flash is another one of those four. And we test it pretty much like we tested assigns.

Paul: OK. What about the failure path? If the save is successful, the controller uses redirect_to. But if it fails, it uses render. What's the difference?

CB: That's a good question, Paul. The difference is important. The redirect_to starts a new request/response cycle. It generates a new request, and Rails treats that request just as if it were coming from the browser. That means the controller method gets invoked and then the instance variables that it creates get passed to the view for rendering of the response. In this case, that would mean a new @categories object would be created and everything the visitor had entered would be lost. The render, on the other hand, doesn't invoke another controller method. It tells Rails to use the instance variables that this method created, but render the response using this other template. We have to be careful when we use render to make sure that the instance variables that the template expects all exist. Otherwise, when the visitor submits that page it might not contain all the value we expect it to contain. In this case, the new view only expects @categories. By using render instead of redirect_to we're using the @categories object we just created using the information the visitor just entered. Some of that information is incorrect, which caused the validations to fail, and we're passing it all back to them so they can correct the problems. If you want to fire up the app and enter a name for the category that's longer than 100 characters so it fails our validation, you'll see what I mean. ;-)

Paul: That's OK. Maybe later. I think I understand the difference. So, to test this, we just need to make sure the correct template's being used. Lemme take a stab at this. I need to add a test for the flash on the success path, and tests to make sure a record didn't get saved and to make sure the right template was used for the failure path. I think our new test method, or rather our newly named test method, needs to look like...

def test_create_success_and_failure
   num_categories = Category.count
   post :create, :category => {:name => "new category"}
   assert_response :redirect
   assert_redirected_to :action =>'list'
   assert_not_nil flash(:notice)
   assert_equal num_categories + 1, Category.count

   num_categories = Category.count
   post :create, :category => {:name => ""}
   assert_response :success
   assert_template 'new'
   assert_equal num_categories, Category.count

What do you think?

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Next Pagearrow