Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
Pivotal Labs

Standup 9/11/2009: to_param now required in functional tests

Pivotal Labs
Friday, September 11, 2009

Ask for Help

“Do you have to use to_param in functional tests?”

In the past you could simply provide an object in a param list in functional tests and the to_param for the object would be called to get the proper value for the parameter. This is now broken, forcing you to use object.to_param every time.

Perhaps not helpful for existing projects but I recommend you use cucumber, webrat, or even selenium rather than Rails functional tests. Rails functional tests require that you specify parameters and specify them correctly. If you get them wrong your functional tests might continue to pass for the wrong reason. Here’s another “bad params in functional tests” post.

Interesting Things

  • Rubyconf program should be available today on their website
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Testing Desert Plugins in Isolation

Joe Moore
Saturday, August 22, 2009

At Pivotal, some of our client projects use plugins from our home-grown social networking platform and rely on Desert to tie them all together. To test this package of plugins we created a project that contains all of our Desert plugins and wrote some rake tasks that run all of their tests. Great, right?

Mostly. We want to ensure that our plugins have the absolute minimum dependencies to function. Let’s pretend we have an UserAuth plugin and a SocialPivots plugin, where UserAuth has no dependencies, but SocialPivots depends on UserAuth. We would like to test these the to plugins in isolation. But, with Desert doing it’s job so well, our UserAuth plugin could have a dependency on the SocialPivots plugins’ models or tables and we would never know it. Everything from SocialPivots is mixed-in and loaded into memory, and all of its migrations have executed, at the time we are running UserAuth’s tests.

What we need is a way to tell Desert to load only the plugin under test, plus its dependencies listed in init.rb. Hacking Desert and Rails to allow us to specify which plugins to load turned out to be pretty easy. Check it out (full gist here):

Here, we override plugin loading:

# lib/plugin_dependency_limiter.rb
class Rails::Initializer
  def load_plugins
    # Only load the plugin under test
    Rails::Plugin.new("vendor/plugins/#{ENV['PLUGIN']}").load(self)
  end

  def add_plugin_load_paths
    # Do nothing.  We'll handle plugin loading ourselves.
  end
end

We might have many plugins in vendor/plugins but we only want to test our Desert plugins. We list them in config/plugins/plugins_to_test.yml and keep track of them here:

# also in lib/plugin_dependency_limiter.rb
class Rails::Plugin
  def self.plugins_of_interest
    # Keep track of the Desert plugins we care about
    @of_interest ||= YAML.load_file(RAILS_ROOT + "/config/plugins/plugins_to_test.yml").collect
  end

  def self.tracked_plugins
    @tracked_plugins ||= []
  end

  def require_plugin_with_plugin_tracking(plugin_name)
    self.class.tracked_plugins << plugin_name if self.class.plugins_of_interest.include?(plugin_name)
    require_plugin_without_plugin_tracking(plugin_name)
  end
  alias_method_chain :require_plugin, :plugin_tracking
end

Once you can control which plugins are loaded you can expand this to dictate which routes and migrations should be run:

Routes:

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  if ENV['PLUGIN']
    Rails::Plugin.tracked_plugins.each do |plugin|
      map.routes_from_plugin(plugin)
    end
    map.routes_from_plugin(ENV['PLUGIN'])
  end
end

Migrations:

# db/migration/001_migrate_desert_plugins.rb
class MigrateDesertPlugins < ActiveRecord::Migration
  def self.up
    Rails::Plugin.tracked_plugins.each do |plugin|
      migrate_plugin(plugin, nil)
    end
    migrate_plugin(ENV['PLUGIN'], nil)
  end

  def self.down
    raise ActiveRecord::IrreversibleMigration
  end
end

Let’s run some tests! Though we don’t provide the rake task here, it runs db:drop db:create db:migrate db:test:prepare before running the plugin tests.

First, the UserAuth plugin that has no dependencies:

$ rake testspec:plugins PLUGIN=user_auth
(in /Users/pivotal/workspace/desert_plugins)
==  MigrateDesertPlugins: migrating ===========================================
==  CreateUserAuthStuff: migrating ====================================================
-- create_table(:user_auth_stuff)
   -> 0.0023s
==  CreateUserAuthStuff: migrated (0.0026s) ===========================================

==  MigrateDesertPlugins: migrated (0.0076s) ==================================

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I"lib:test" "/Library/Ruby/Gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb"
....................................

Finished in 0.494227 seconds

36 examples, 0 failures

Now the SocialPivots Plugin, which relies on UserAuth:

$ rake testspec:plugins PLUGIN=social_pivots
(in /Users/pivotal/workspace/desert_plugins)
==  MigrateDesertPlugins: migrating ===========================================
# Look, the migrations from UserAuth!
# Look, the migrations from UserAuth!
# Look, the migrations from UserAuth!
==  CreateUserAuthStuff: migrating ====================================================
-- create_table(:user_auth_stuff)
   -> 0.0201s
==  CreateUserAuthStuff: migrated (0.0204s) ===========================================

==  CreateSocialPivotStuff: migrating ==============================================
-- create_table(:social_pivot_stuff)
   -> 0.0034s
==  CreateSocialPivotStuff: migrated (0.0036s) =====================================

==  MigrateDesertPlugins: migrated (0.0332s) ==================================

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I"lib:test" "/Library/Ruby/Gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb"
........................................................................

Finished in 1.381141 seconds

72 examples, 0 failures

I hope this code makes testing Desert plugins easier. Thanks to Pivots Adam and Edward for pairing with me through these solutions.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Will Read

How to Not Test RabbitMQ Part 2

Will Read
Friday, August 7, 2009

This is Part 2 of my two part series on working with queues in Ruby. If you want some context please head over to part 1. In this post I’ll touch on Moqueue, using RSpec to stub out Bunny, and a few other hurdles along the way.

So getting started, we didn’t want our unit tests to hit RabbitMQ because we feel strongly that our unit tests shouldn’t depend on outside services any more than necessary (disclaimer: I do feel strongly that our integration tests should depend on those services to recreate the state of the world as accuratly as possible). To do this, there’s a great mock called Moqueue which Chris has kept in our field of vision.

In the example spec for what’ll become our subscriber daemon, it looks like something like this:

require "subscriber_daemon"
require 'moqueue'

describe SubscriberDaemon do
  before :all do
    overload_amqp
  end
  ...
end

The key is this line:

overload_amqp

The way I think about this is that it injects a overgrown array where I call MQ.new. Sweet, now I can leave RabbitMQ out of my tests.

Next, my SubscriberDaemon looks something like this:

require 'simple-daemon'
require 'mq'

class SubscriberDaemon < SimpleDaemon::Base
  SimpleDaemon::WORKING_DIRECTORY = "#{BOX.log_dir}"
  READ_QUEUE = "MyQueueOfWork"

  def self.start
    puts "STARTING #{classname} #{Time.now}"
    STDOUT.flush
    EM.run do
      subscribe_to_read_queue
    end
  end

  def self.stop
    puts "STOPPING #{classname} #{Time.now}"
    STDOUT.flush
  end

  def self.subscribe_to_read_queue
    amq = MQ.new
    queue = amq.queue(READ_QUEUE, :durable => true, :auto_delete => false, :exclusive => false)
    queue.subscribe(:ack => true) do |header, message|
      #stuff the message in the database
      ...
      header.ack
    end
  end

Some things to note: We used SimpleDaemon to get us daemon functionality. The start and stop methods are part of that interface. It needs you define a working directory for the log file and pid file it generates for you. Works great with Monit (make sure to use the latest version or Monit will start up multiple instances of your daemon). It also likes to spool up the messages so if you want feedback in your log that resembles real time events, make sure to flush your output.

You’ll also see that we created a durable queue, meaning stuff stays on the queue (in a “being worked on state” until it gets an ack back. you’ll also see that our subscribe method has to be called inside an EM.run. This is because of the async nature of the AMQP gem’s client queue subscribe method.

First potential pitfall: Sticking your subscribe code inside the EM.run. You’ll never know when it completes. It is also hard to stop. Just extract that logic out into a method and you’ll be as happy as two rabbits on their “bunnymoon”.

Onward, now lets look at some tests around the SubscriberDaemon.

before :all do
  overload_amqp
end

it "should read a item from the queue and stuff it in the database" do
    #put some stuff on the queue
    amq = MQ.new
    queue = amq.queue(SubscriberDaemon::READ_QUEUE)
    queue.publish("My message")

    #do the subscription
    SubscriberDaemon.subscribe_to_read_queue

    #assert on the expected outcome (eg look it up in the database)
    ...
  end

In this case, we’ve written the test to exercise the queue client, but not the server. We fake out RabbitMQ using Moqueue, and we never start the daemon, so we don’t have to worry about stopping it, we simply call the same method the daemon calls in the loop. You may also notice we explicitly put “My message” on the queue in the spec.

Done. SubscriberDaemon unit test is written.

On the other end, we want to publish messages with the string reversed to the queue. So here’s a MessageMaker:

require 'subscriber_daemon'

class MessageMaker
  SUCCESS_QUEUE = SubscriberDaemon::READ_QUEUE

  def self.log(message)
    puts caller.first
    puts Time.now
    puts message
    STDOUT.flush
  end

  def self.make_reversed_message(message="Hello Readers!")
    amqp_client = Bunny.new(:logging => false)
    amqp_client.start
    queue = amqp_client.queue(SUCCESS_QUEUE, :durable => true, :auto_delete => false, :exclusive => false)
    queue.publish(message.reverse, :persistent => true)
    MessageMaker.log("PUBLISHED #{message.reverse}")
    amqp_client.stop
  end
end

In our spec for MessageMaker, let us assume that we just want to test the make_reversed_message() logic and leave all the queue nonsense out of it. This means we’ll have to mock Bunny. For a little extra excitement, let’s call make_reversed_message() twice to ensure that making a message doesn’t pollute the next message that gets made.

require 'bunny'

describe MessageMaker, "#make_message" do
  it "doesn't pollute subsequent messages"
    bunnies = []
    bunnies << mock('bunny')
    bunnies[0].stub(:start)
    bunnies[0].stub(:stop)
    bunnies[0].stub(:exchange)
    bunnies[0].stub(:queue).and_return do |*args|
        queue = mock('queue', :name => 'my queue')
        queue.should_receive(:publish).with("?ykcits dna nworb s'tahW", :persistent => true)
        queue
    end

    bunnies << mock('bunny')
    bunnies[1].stub(:start)
    bunnies[1].stub(:stop)
    bunnies[1].stub(:exchange)
    bunnies[1].stub(:queue)
    bunnies[1].stub(:queue).and_return do |*args|
      queue = mock('queue', :name => 'my queue')
      queue.should_receive(:publish).with("!kcits A", :persistent => true)
      queue
    end

    Bunny.stub(:new).and_return do |*args|
      bunnies.shift #shifts off the first instance in the array and returns it
    end

   MessageMaker. make_reversed_message("What's brown and sticky?")
   MessageMaker. make_reversed_message("A stick!")
  end
end

The assertions happen in the mocked Bunny instances where you see “queue.should_receive(…)” We have two bunnies because each time you call make_reversed_message() it instantiates a new Bunny. We stubbed Bunny.initialize() to return different instances with different expectations when queue() is called.

So now we have a very focused unit test which only tests the logic of make_reversed_message(). It doesn’t test any queue related code because we mocked out Bunny, our AMQP Client that we use for adding our messages to the queue synchronously, but demonstrates that the message does get reversed. And that, is the way to not test your queues.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Will Read

How To Not Test RabbitMQ Part 1

Will Read
Thursday, August 6, 2009

Q: How do you catch a unique rabbit?
A: Unique (you-neek) up on it!
Q: How do you catch a tame rabbit?
A: Tame way!!!!!

We’ve been using RabbitMQ as a queue server, alongside the clients, Bunny, and AMQP. In this series I’m hoping (hopping?) to show you some of the pitfalls we’ve learned to avoid and talk about how to write tests that test your code without getting stuck running a queue server in your test environment.

In Part 1 I’ll focus on our situation and creating some context around our choices so that you can decide what makes sense for your project. In Part 2, I’ll get into the nitty gritty of how to write some tests/specs around your queues.

First off, why queues? We’re making a shift from processing a million documents at a time, to processing one document at a time. The problem before was that if one document failed, they all failed, and we had to start all over. We needed to move to a point where we could single out trouble makers, and keep things in the last successful state so we didn’t have to go back to step one every time.

Different steps take different amounts of time, so invariably we would have to find ourselves queuing up the work that had to be done. Hey… sounds like I need a queue. Right.

We came up with RabbitMQ as our queue server solution.

  • It is field tested, which inspires confidence
  • Seems to have a good user base/community to help us get over the learning curve
  • Provides durable queues, meaning that if the server goes down, the state of the queue is preserved
  • Has a transactional-type mechanism, where things do not come all the way off the queue until an acknowledgment is sent back to the queue saying that our process is done with this piece of work.
  • Can route work around based on rules set up in RabbitMQ which responds to the content of the piece of work.

We did not do a whole lot of looking into other queue servers, so I can tell you right now, when you ask “Did you look into using Queue Server X?” my answer will probably be “Nope.”

We got RabbitMQ Server building in Chef and monitored by Monit (via Chef again) and we had a server running easily without any hurdles.

So next we need some clients. We started where everyone else seemed to start with the AMQP gem. It’s a robust little gem that seems to implement all the parts of the AMQ protocol we were interested in. It also does publish and subscribe actions asynchronously! The only problem is that it does publishes and subscribes asynchronously! o_O

AMQP Client works great on the subscribe side because you get real events, meaning you don’t have to poll the queue all the time to see if there’s work to be done. Of course, you never know when or for how long those pieces of work will take to be processed, so you’ll see how this affects one’s tests later on.

The AMQP Client gem makes use of Event Machine, which Joseph talked about in his blog about how the Event Machine loops in a somewhat unexpected way. It also has the problem that it’ll publish your data, and then forget about it, which is not so good if your server isn’t running because you never get an ack back that your work is on the queue.

Enter the Bunny Client gem (there’s also a Carrot client out there, these queue people apparently love their cotton-tailed references). Much like the AMQP Client gem, Bunny appears to be a full implementation of the AMQ Protocol, only this time things happen synchronously. This worked great for throwing things onto our queue because we didn’t need one job to be forking all over the place, and we now got feedback if Bunny couldn’t connect to the server. But as you’ll see in Part 2, testing still wasn’t without challenges.

So we arrived at RabbitMQ as our queue server, Bunny gem as our client of choice for publishes, and AMQP gem as our subscriber client. The solution seemed to be working well, but test driving it was a learning experience, trying to find out what needed mocking, what needed a stub, and understanding what we didn’t want to test at all. Next time in Part 2 I’ll show you the patterns we derived, and we’ll hop right along some example Ruby code and RSpec specs.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Adam Milligan

Worst case scenario

Adam Milligan
Monday, August 3, 2009

Years ago, after I finished college but before I started working professionally with software, I spent a couple years working as a paramedic. I learned a lot from that job, not least about interacting with people who really, really don’t want you in their lives.

One of the calls I remember most vividly happened around three in the morning, not long after schools had let out for the summer. A group of recently graduated high school girls had rolled their Ford Bronco on the highway. When we arrived an engine company was on scene, busily cutting the remains of the car into fun size Bronco strips. I followed the trajectory implied by the hole in the windshield and found my patient, the driver, on the pavement some distance from the car.

While I set about preparing to package her up for a quick trip to the hospital another engine company arrived. As I started my cursory physical exam the lieutenant rushed over and demanded I stop. To understand his reasoning you have to realize that the process of emergency medicine affords little or no dignity to trauma patients: life comes before limb; modesty comes well down the list.

So, what the fire lieutenant objected to was that I was cutting the clothes off a sixteen year old girl in the middle of the highway, directly illuminated by the halogen scene lights from our bus. He demanded that we package and transport the patient fully clothed. If you haven’t worked with firefighters, know this: they travel in packs. My partner was hanging IV bags and working the radio for another bus, so it was me, a supine patient, and five firefighters. They got what they wanted.

Of course, to protect trauma patients’ spines you have to package them fairly thoroughly. You basically strap them down to a six foot board so they can’t move, and once you finish it’s pretty much impossible to get their clothes off or do a half decent physical exam without jeopardizing their spinal cords. Which means, as the attending medic, when we got to the ED I was the one who had to explain to the trauma surgeon and ED physician why I was handing over a patient who could have had a piece of windshield glass the size of a grapefruit sticking into her kidney for all I knew. Not a shining career moment.

I remember that call not because the patient was badly hurt (just some broken bones; she was lucky), or because I made a huge difference in someone’s life, but because of the lieu’s argument for not doing the full, expected, physical exam. As his minions packaged my patient like gift-wrappers at Macy’s, and my partner made a break for the driver’s seat, he told me these exact words:

“She’s suffered enough trauma. She’ll be okay.”

Now, I knew at the time he was quite likely right (her chances having escaped major injuries were actually better than you might imagine), and it’s actually quite difficult to explain to patients why you have to cut their clothes off (try it some time), poke them, shock them, or tie them down. It’s very, very tempting to do the easy thing in these cases, and 99 out of 100 times if you avoid making the patient, or yourself, uncomfortable they do fine. But, one out of 100 times the patient dies from a ruptured spleen or flash pulmonary edema. That’s the worst case scenario; and that’s the only scenario medics really care about.

Now (finally on to my point, dear reader), I recently submitted a patch to Rails that I, and many of my colleagues, believe will help prevent invalid functional tests, and therefore prevent bugs. The response from the Rails core team:

The vast bulk of tests just pass the id in those places and they’ll work fine. Users overloading to_param are in the minority and we shouldn’t spam everyone else just to satisfy them.

Ignoring for a moment the somewhat unscientific characterization of the prevalence of #to_param overriding, the message is that the patch is potentially annoying for people with improperly written tests and most of those improperly written tests probably won’t be a problem. It’s more comfortable for the Rails team to let broken tests slide and hope all will be well, than to bother developers over the cases in which broken tests lead to broken production.

I realize that comparing HTTP 500 responses to ruptured spleens might seem overly draconian. But, consider this: while the consequences of doing the wrong thing in software are much less dire compared to medicine, it’s so much easier to do the right thing. If you doubt this, drop me a line; I’ll be happy to nasally intubate you. I guarantee after that experience spending half an hour fixing your controller tests won’t seem so bad.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Adam Milligan

More on Wapcaplet

Adam Milligan
Sunday, August 2, 2009

Yesterday I wrote about Wapcaplet, which is really little more than a Rails patch that didn’t get accepted, but that some of us think Rails actually quite needs. To that end I submitted a second patch, which does the same thing but, by default, outputs a warning rather than raising an exception. I also included some methods for modifying the behavior on ActionController::TestCase. Specifically, if you want to ensure your tests aren’t broken:

ActionController::TestCase.treat_parameter_type_warnings_as_errors

Or if you, like Pierre, don’t care:

ActionController::TestCase.ignore_parameter_type_warnings

I don’t know if these changes will make the behavior of the patch palatable enough for the core team to commit it. We’ll see. After creating the ticket I considered pulling the new behavior back into Wapcaplet; I’ve decided not to for a few reasons:

  • First and foremost, no one pays attention to warnings. I can’t count the times I’ve preached myself blue about eliminating compiler/interpreter warnings, to little or no effect. I recently broke the builds for several projects by deleting a method that had been deprecated for a year and half, and which generated a fairly annoying deprecation warning on every build for every project that used it (keep in mind that at Pivotal projects will build many times a day).

  • Any patch applied to Rails will affect every Rails project that upgrades. I believe people should fix their broken tests, but I accept that this change will break a lot of tests. I can accept warnings as a way to show people what may be broken without bringing the world down on their heads. Wapcaplet, on the other hand, is entirely opt-in; no need to handle users with kid gloves.

  • I believe that a test failure is the right behavior. We’re talking about broken tests, they should act that way.

Remember, the lion ate Pierre.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Adam Milligan

Wapcaplet

Adam Milligan
Saturday, August 1, 2009

Imagine for a moment that you run a big, important company. It’s important to you that your big, important company be successful at promoting, manufacturing, and distributing your big, important product, so you have decreed that the company must show a profit each and every quarter. In fact, your internal accounting software enforces this. For example:

describe "QuarterlyReportsController#create" do
  it "should reject quarterly reports that show a net loss" do
    post :create, :quarterly_report => { :net => -100 }
    response.response_code.should == 400
  end
end

Ignoring the somewhat misguided domain requirements, this test is wrong because it probably won’t fail when it should. It’s an example of a problem in Rails controller testing that bites everyone sooner or later.

The problem is that HTTP requests don’t send their parameters as integers, or booleans, or Date objects. No, HTTP request parameters are just big piles of strings. Rails does a good job of hiding the process of converting these strings into the integers and booleans and Date objects that make sense in your domain, but ActiveRecord handles that little bit of sleight of hand (using the column types from your database schema), not ActionController.

So, when you execute that test up above, the value of params[:quarterly_report][:net] will be the integer value -100, which is a value that the controller will never receive from a real HTTP request. This test fails to test a real case.

Now, if you try to use this value as an integer, either in the controller or by overriding #net= in the model, the test will still pass. But, as soon as you send a real request to the controller (hopefully not in production) you’ll find yourself on the business end of a 500 response. The test is broken.

In order to prevent this sort of brokenness I wrote a small patch to Rails (available here), which was summarily, and I will admit not unexpectedly, rejected. After that, I turned the patch into a tiny plugin called Wapcaplet (available here). It simply checks the parameters you pass to functional tests, and throws a friendly exception if you pass something with an inappropriate type. It accepts strings and instances of TestUploadFile in all cases. For parameter that Rails uses for routing it will also accept subclasses of ActiveRecord::Base.

Some examples:

# POST users/
post :create, :user => { :likes_ice_cream => true } # BOOM
post :create, :user => { :likes_ice_cream => "1" } # OK

post :create, :user => { :best_friend => @other_user } #BOOM
post :create, :user => { :best_friend_id => @other_user.id } # BOOM
post :create, :user => { :best_friend_id => @other_user.id.to_s } #OK

# GET users/:id
get :show, :id => @user.id # BOOM
get :show, :id => @user.to_param # OK
get :show, :id => @user # OK

# PUT users/:user_id/profile
put :update,
  :user_id => @user,
  :profile => { :photo => File.open("some/file" } # BOOM
put :update,
  :user_id => @user,
  :profile => { :photo => fixture_file_upload("some/file", "image/jpg" } #OK

Incidentally, notice that this will catch the pathological case, which seems to afflict every Rails developer, in which you pass #id rather than #to_param for a routing parameter.

In anticipation of the misguided comments that I know will come, no, it would not be better to have the plugin (or patch) call #to_s or #to_param on every incoming parameter to functional tests. Consider the example of boolean values:

true.to_s # => "true"
false.to_s # => "false"
true.to_param # => true
false.to_param # => false

Neither #to_s nor #to_param return a value likely to appear in a real HTTP request. It doesn’t take much imagination to come up with other examples of types that would not convert to meaningful request values. Worse, it takes only a little more imagination to come up with a scenario in which the implicit string conversion in the test would create subtlely wrong behavior that would be an enormous pain to track down.

So, take Wapcaplet out for a spin, I hope it saves you some time. Special thanks to Parker for getting bit by this problem enough times to get angry and demand I fix it.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Jacob Maine

Standup 7/27/2009: Testing Partial Locals

Jacob Maine
Monday, July 27, 2009

Ask for Help

“What’s the best way to test that a controller (a) renders a partial and (b) passes the partial the right locals?”

Mocking render_template and the locals ends up mocking too much of the request cycle. Ideally, we’d like something like:

response.should render_partial("item", :locals => {:item => assigns[:item]})

“Hpricot breaks with xml elements named param. Any suggestions?”

Works if we replace Hpricot with Nokogiri, so that may be the way to go.

Interesting Things

  • A follow up on the problems using Fixture Scenarios with Rails 2.3.2: The fixture are loading twice, although it’s not clear why. The second run barfs with a syntax error. Perhaps the first run doesn’t properly close the fixture file?
  • Raising the timeout fixed issues with S3 uploads over slow connections. It might also be more efficient. The timeout could stop a 90% complete upload, then kick off a retry (the gem has a 3 retry policy), which further clogs the tubes.

Update: The Hpricot problem is with elements named param, not params as I originally stated.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 07/22/2009: Temporarily Redefine a Method

Pivotal Labs
Wednesday, July 22, 2009

Ask for Help

“Is there a good way to temporarily redefine a method on a controller during a functional test?”

Reopening a controller and overriding a method affects all tests in a suite. Is there a good way to redefine a controller method for a single test?

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Using assert_select and friends in Helper Tests

Pivotal Labs
Monday, July 6, 2009

Assertions such as assert_select, assert_tag, and css_select are powerful tools in view tests. Since view helpers generate a chunk of HTML, it is sometimes practical to use these assertions to test their return value.

If you attempt to use assert_select it will fail because there has not been a call to render, as is done in a view test. To get around this you can store the result of the helper method in @response.body. Once @response.body is populated, the assertions will work like expected.

Update: For people using RSpec, Pat Maddox posted a helpful suggestion in the comments showing how to use have_tag to accomplish the same thing in RSpec style.

Example

describe TagsHelper do
  describe "#tag_list"
    describe "when the user is not an admin"

      it "should not have links to delete the tags" do
        @response.body = helper.tag_list
        assert_select "a", 0
      end

    end
  end
end

Reality Check

If you find yourself writing a helper method that generates a lot of HTML, or is overly complex, stop and ask yourself whether it would be more appropriate to move this code into a partial.

Attribution

Credit for this tip goes to a comment by Bill Siggelkow on the Nuby on Rails blog.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (781)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (21)
  • cucumber (20)
  • design (19)
  • jasmine (19)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • tracker ecosystem (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to testing Feed
  1. ←
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9
  11. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >