Recipe 7.22. Modifying the Default Behavior of a Class for Testing by Using Mocks


Recipe 7.22. Modifying the Default Behavior of a Class for
Testing by Using Mocks

Problem

Contributed by: Blaine Cook

You have behavior that has undesirable side effects in your test or development environments, such as the unwanted delivery of email (e.g., from Section 3.15"). Adding extra logic to your model or controller code to prevent these side effects could itself lead to bugs that are difficult to isolate, so you'd like to specify this alternate behavior elsewhere.

Solution

Rails provides a special include directory you can use to make environment-specific modifications to code. Because Ruby allows class and module definitions to happen in different files and at different times, we can use this facility to make narrow modifications to our existing classes.

For example, in Section 3.15" we implemented a SubscriptionObserver that executes the system's mail command. The mail command isn't present on Windows machines. Be careful when testing; you may send large volumes of mail to unsuspecting victims.

app/models/subscription_observer.rb:

class SubscriptionObserver < ActiveRecord::Observer   def after_create(subscription)     `echo "A new subscription has been created (id=#{subscription.id})" |           mail -s 'New Subscription!' admin@example.com`   end end

You can override this behavior by creating a new file subscription_observer.rb in test/mock/test/:

test/mock/test/subscription_observer.rb:

include 'models/subscription_observer.rb' class SubscriptionObserver   def after_create(subscription)     subscription.logger.info(       "Normally we would send an email to "admin@example.com telling " +       "them that a new subscription has been created " +       "(id=#{subscription.id}), but since we're running in a test " +       "environment, we'll refrain from spamming them.")   end end

With this code in place, you'll get a message in log/test.log indicating that the observer code was executed. You can see it in action by running the following test:

test/functional/subscriptions_controller_test.rb:

require File.dirname(__FILE__) + '/../test_helper' require 'subscriptions_controller' # Re-raise errors caught by the controller. class SubscriptionsController; def rescue_action(e) raise e end; end class SubscriptionsControllerTest < Test::Unit::TestCase   def setup     @controller = SubscriptionsController.new     @request    = ActionController::TestRequest.new     @response   = ActionController::TestResponse.new   end   def test_create     post :create, :subscription => {                      :first_name => 'Cheerleader',                      :last_name => 'Teengirl',                      :email => 'cheerleader@teengirlsquad.com' }     assert_redirected_to :action => 'list'   end end

Run this test with the command:

$ ruby test/functional/subscriptions_controller.rb -n 'test_create'             

Now check the logged output with:

$ grep -C 1 'admin@example.com' log/test.log             

You should see three lines: the first is the SQL insert statement that created the record, the second is the log message indicating that the after_create observer method was called, and the third indicates a redirection to the list action.

Discussion

On the first line of the mock object, we explicitly include our original subscription_observer.rb model code. Without this line, we would skip loading any other methods contained in the SubscriptionObserver class, potentially breaking other parts of the system. While this counts as a potential gotcha, it serves an important purpose: not autoloading the corresponding real versions of mocked classes means that we can create mocks of just about any code in our Rails environment. Models, controllers, and observers are all easily mocked. Just about the only things that can't be mocked are your application's views.

Because we send the email via the Unix mail command, it's hard to test for success without introducing harmful dependencies into the tests. Stubbing out the behavior offers a simple way to ensure that our tests don't get in the way.

See Also

  • There is some debate about the terminology of mocks, as discussed by Martin Fowler at http://www.martinfowler.com/articles/mocksArentStubs.html. However, the term "mock" is used here to refer to objects whose behavior has been modified to facilitate testing, because this is how Rails uses it.




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net