
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index

USING MOCK OBJECTS 161
When you’re debugging tests, it’s incredibly helpful to watch the log/test.log file. For functional tests, the log file gives you an end-to-end view inside of your application as it goes through the motions.
Phew, we quickly cranked out a few tests there. It’s not a very comprehensive suite of tests, but we learned enough to write tests until the cows come home. Should we drop everything and go write tests for a while? Well, we took the high road on most of these, so writing a few tests off the beaten path certainly wouldn’t hurt. At the same time, we need to be practical and write tests for those things that are most likely to break first. And with the help Rails offers, you’ll find that indeed you do have more time to test.
12.4 Using Mock Objects
At some point we’ll need to add code to the Depot application to actually collect payment from our dear customers. So imagine that we’ve filled out all the paperwork necessary to turn credit card numbers into real money in our bank account. Then we created a PaymentGateway class in the file app/models/payment_gateway.rb that communicates with a credit-card processing gateway. And we’ve wired up the Depot application to handle credit cards by adding the following code to the save_order( ) action of the
StoreController.
gateway = PaymentGateway.new |
|
response = gateway.collect(:login |
=> 'username', |
:password |
=> 'password', |
:amount |
=> cart.total_price, |
:card_number |
=> @order.card_number, |
:expiration |
=> @order.card_expiration, |
:name |
=> @order.name) |
When the collect( ) method is called, the information is sent out over the network to the backend credit-card processing system. This is good for our pocketbook, but it’s bad for our functional test because the StoreController now depends on a network connection with a real, live credit-card processor on the other end. And even if we had both of those things available at all times, we still don’t want to send credit card transactions every time we run the functional tests.
Instead, we simply want to test against a mock, or replacement, PaymentGateway object. Using a mock frees the tests from needing a network connection and ensures more consistent results. Thankfully, Rails makes mocking objects a breeze.
Prepared exclusively for Rida Al Barazi
Report erratum

|
TEST -DRIVEN DEVELOPMENT |
162 |
|
To mock out the collect( ) method in the testing environment, all we need |
|
|
to do is create a payment_gateway.rb file in the test/mocks/test directory that |
|
|
defines the methods we want to mock out. That is, mock files must have |
|
|
the same filename as the model in the app/models directory they are replac- |
|
|
ing. Here’s the mock file. |
|
File 120 |
require 'models/payment_gateway' |
|
|
class PaymentGateway |
|
|
def collect(request) |
|
|
# I'm a mocked out method |
|
|
:success |
|
|
end |
|
|
end |
|
|
Notice that the mock file actually loads the original PaymentGateway class |
|
|
(using require( )) and then reopens it. That means we don’t have to mock out |
|
|
all the methods of PaymentGateway, just the methods we want to redefine |
|
|
for when the tests run. In this case, the collect( ) simply returns a fake |
|
|
response. |
|
|
With this file in place, the StoreController will use the mock PaymentGateway |
|
|
class. This happens because Rails arranges the search path to include |
|
|
the mock path first—test/mocks/test/payment_gateway.rb is loaded instead |
|
|
of app/models/payment_gateway.rb. |
|
|
That’s all there is to it. By using mocks, we can streamline the tests |
|
|
and concentrate on testing what’s most important. And Rails makes it |
|
|
painless. |
|
12.5 Test-Driven Development
So far we’ve been writing unit and functional tests for code that already exists. Let’s turn that around for a minute. The customer stops by with a novel idea: allow Depot users to search for products. So, after sketching out the screen flow on paper for a few minutes, it’s time to lay down some code. We have a rough idea of how to implement the search feature, but some feedback along the way sure would help keep us on the right path.
That’s what test-driven development is all about. Instead of diving into the implementation, write a test first. Think of it as a specification for how you want the code to work. When the test passes, you know you’re done coding. Better yet, you’ve added one more test to the application.
Let’s give it a whirl with a functional test for searching. OK, so which controller should handle searching? Well, come to think of it, both buyers
Prepared exclusively for Rida Al Barazi
Report erratum

TEST -DRIVEN DEVELOPMENT |
163 |
|
and sellers might want to search for products. So rather than adding |
|
a search( ) action to store_controller.rb or admin_controller.rb, we generate a |
|
SearchController with a search( ) action. |
|
depot> ruby script/generate controller Search search |
|
There’s no code in the generated search( ) method, but that’s OK because |
|
we don’t really know how a search should work just yet. Let’s flush that |
|
out with a test by cracking open the functional test that was generated for |
|
us in search_controller_test.rb. |
File 118 |
require File.dirname(__FILE__) + '/../test_helper' |
|
require 'search_controller' |
|
class SearchControllerTest < Test::Unit::TestCase |
|
fixtures :products |
|
def setup |
|
@controller = SearchController.new |
|
@request = ActionController::TestRequest.new |
|
@response = ActionController::TestResponse.new |
|
end |
|
end |
|
At this point, the customer leans a little closer. She’s never seen us write a |
|
test, and certainly not before we write production code. OK, first we need |
|
to send a request to the search( ) action, including the query string in the |
|
request parameters. Something like this: |
File 118 |
def test_search |
|
get :search, :query => "version control" |
|
assert_response :success |
|
That should give us a flash notice saying it found one product because the |
|
products fixture has only one product matching the search query. As well, |
|
the flash notice should be rendered in the results.rhtml view. We continue to |
|
write all that down in the test method. |
File 118 |
assert_equal "Found 1 product(s).", flash[:notice] |
|
assert_template "search/results" |
|
Ah, but the view will need a @products instance variable set so that it |
|
can list the products that were found. And in this case, there’s only one |
|
product. We need to make sure it’s the right one. |
File 118 |
products = assigns(:products) |
|
assert_not_nil products |
|
assert_equal 1, products.size |
|
assert_equal "Pragmatic Version Control", products[0].title |
|
We’re almost there. At this point, the view will have the search results. But |
|
how should the results be displayed? On our pencil sketch, it’s similar |
|
to the catalog listing, with each result laid out in subsequent rows. In |
Prepared exclusively for Rida Al Barazi
Report erratum

|
TEST -DRIVEN DEVELOPMENT |
164 |
|
fact, we’ll be using some of the same CSS as in the catalog views. This |
|
|
particular search has one result, so we’ll generate HTML for exactly one |
|
|
product. “Yes!”, we proclaim while pumping our fists in the air and making |
|
|
our customer a bit nervous, “the test can even serve as a guide for laying |
|
|
out the styled HTML!” |
|
File 118 |
assert_tag :tag => "div", |
|
|
:attributes => { :class => "results" }, |
|
|
:children => { :count => 1, |
|
|
:only => { :tag => "div", |
|
|
:attributes => { :class => "catalogentry" }}} |
|
|
Here’s the final test. |
|
File 118 |
def test_search |
|
|
get :search, :query => "version control" |
|
|
assert_response :success |
|
|
assert_equal "Found 1 product(s).", flash[:notice] |
|
|
assert_template "search/results" |
|
|
products = assigns(:products) |
|
|
assert_not_nil products |
|
|
assert_equal 1, products.size |
|
|
assert_equal "Pragmatic Version Control", products[0].title |
|
|
assert_tag :tag => "div", |
|
:attributes => { :class => "results" }, :children => { :count => 1,
:only => { :tag => "div",
:attributes => { :class => "catalogentry" }}}
end
Now that we’ve defined the expected behavior by writing a test, let’s try to run it.
depot> ruby test/functional/search_controller_test.rb
Loaded suite test/functional/search_controller_test
Started F
Finished in 0.273517 seconds. 1) Failure:
test_search(SearchControllerTest) [test/functional/search_controller_test.rb:23]: <"Found 1 product(s)."> expected but was <nil>.
1 tests, 2 assertions, 1 failures, 0 errors
Not surprisingly, the test fails. It expects that after requesting the search( ) action the view will have one product. But the search( ) action that Rails generated for us is empty, of course. All that remains now is to write the code for the search( ) action that makes the functional test pass. That’s left as an exercise for you, dear reader.
Why write a failing test first? Simply put, it gives us a measurable goal. The test tells us what’s important in terms of inputs, control flow, and outputs before we invest in a specific implementation. The user interface
Prepared exclusively for Rida Al Barazi
Report erratum