Rails Functional testing with Stubs and BDD

Posted by Brian in Rails, snacks, tips (July 26th, 2007)

One of the things that bothers many people about functional tests with Rails is the seemingly unnecessary duplication of test coverage.

Consider this test, a very common “create new user” functional test:

def test_create
   post :create, {:user=>{:username => "brian",
                         :password =>"1234", 
                         :password_confirmation=>"1234", 
                         :email=>"hoganbp@uwec.edu"}}
   assert assigns(:user)
   assert_redirected_to :action=>"list"
   
   
end

This is an example of test-driven development at the controller level. You pass in the POST parameters so that the controller can create the user object and save it to the database. This then triggers the redirect.

But this is really kinda bad. You already know that you can save records, you’ve got unit tests that prove that your validation works. You’re really interested in how the controller responds when it creates a valid record.

Behaviors

Behavioral driven development (BDD) is a technique receiving a lot of attention lately in the Rails community. The idea is to write your tests looking at the behavior you’re trying to test, as opposed to just testing methods.

Take a look at the behavior of the controller. What’s it supposed to do?

When a new user is created successfully, it should go to the list action.

So… let’s rename that scaffolded functional test to

   def test_should_redirect_to_list_when_user_is_created

   end

In BDD, you use words like should to specify what should happen. See, already your test is less ambiguous. Eventually you’re going to have to test that it “should_render_registration_form_when_creation_fails” or something like that.

Stubs

We already know from unit tests we wrote that we can save user records. We don’t need to test that again. Good tests don’t cover more than one piece of functionality anyway. How do we get around to it?

Enter http://rubyforge.org/projects/mocha/. Mocha is a mocking library. You can read more about it in the documentation, but I’ll show you how to use the stubbing features to bypass creating records.

Install mocha.

  gem install mocha

Open up your test/test_helper.rb and add

  require 'mocha'

to the top of the file.

This simple bit of code added to our test makes it all work:

   User.any_instance.stubs(:save).returns(true)

You would read that as “Any instance I create of User should have its save method always return true when I call it.”

That means our functional test becomes

def test_should_redirect_to_list_when_user_is_created
   User.any_instance.stubs(:save).returns(true)
   post :create
   assert_redirected_to :action=>"list"
   
end

Wrapping up

Stubbing helps you decouple your methods, and BDD helps you think about your process rather than your code. I encourage you to read more about both. Keep in mind you still want to test other aspects of your controllers, and sometimes you might want to ensure that certain things work certain ways. For example, you might still need @user to contain real data when you do searches so your views will render properly. This is just an example of the power of stubbing.

We’re doing audits too!

Posted by Brian in News (July 17th, 2007)

I’ve noticed that some other firms are starting to market this service, so we’re throwing our hat in the ring too. We’ve been doing this as part of our consulting for about a year now, helping a couple of teams learn how to write better code. This is the next logical step.

It never hurts to have a second set of eyes look over your code before you roll out that brand new application. I routinely have others look over my code before I release it because I know I’m going to miss something. Whether you hired out the project or did it yourself internally, you just might benefit from this service.

We’ll look for things like security vulnerabilities, code that’s not covered by tests, and help you identify trouble spots with your app long before your customers find them.

Contact us today to see how we can help.

Making it easier to install gems on Windows

Posted by Brian in Rails, snacks (July 12th, 2007)

Update 2009-11-15 This is out of date. There’s no reason to read further. Rubygems does OS detection automatically now.

When you install gems, you’re usually asked what version you want to install. Windows users have to choose the one that says ‘mswin32’. This can be really, really awkward for people who are new to this. You have to admit that this is confusing for a first-time Rails on Windows user:

Select which gem to install for your platform (i386-mswin32)
 1. mongrel 1.0.1 (mswin32)
 2. mongrel 1.0.1 (ruby)
 3. mongrel 1.0 (mswin32)
 4. mongrel 1.0 (ruby)
 5. Skip this gem
 6. Cancel installation
>

I’ve had lots of students say that they thought they should choose the Ruby one because that’s what they’re programming in.

I found a great example at http://revolutiononrails.blogspot.com/2007/06/code-digest-2.html by Todd Fisher that explained how to skip the os and version selection on Linux, so I modified it slightly to work with Windows and I’ve added my own method to make installation of gems easier for Windows users.

Here’s how it works: You install gems via a new command on Windows to skip selection of OS and version number.
This will install the gem with dependencies, without documentation. I made the choice to skip doc generation because it’s much faster, but you can modify the script below to take that out, or even make it optional, based on parameters you pass in.

Examples of use:

   gem_install rails
   gem_install rails mongrel mongrel_service capistrano redcloth tzinfo zentest rcov

Here’s the source code for the file. Save this to c:\ruby\bin\gem_install.bat or anywhere on your PATH.


gem_install.bar source code:

@echo off
goto endofruby

#!/bin/ruby
# Cobbled together by Brian Hogan from scripts found at
# http://svn.bountysource.com/fishplate/scripts/debian_install.pl
# and http://revolutiononrails.blogspot.com/2007/06/code-digest-2.html by Todd Fisher.
# Many thanks to their hard work for their solutions that finally made this possible.

    require 'rubygems'
    Gem.manage_gems

    # Patch gems to only install the latest Windows version of the gems.
    Gem::RemoteInstaller.class_eval do
      
      alias_method :find_gem_to_install_without_ruby_only_platform, :find_gem_to_install
      
      def find_gem_to_install( gem_name, version_requirement, caches = nil )
        if caches # old version of rubygems used to pass a caches object
          caches.each {|k,v| caches[k].each { |name,spect| caches[k].remove_spec(name) unless spec.platform == 'ruby' } }
          find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement, caches )
        else
          Gem::StreamUI.class_eval do
            
            alias_method :choose_from_list_without_choosing_ruby_only, :choose_from_list
            def choose_from_list( question, list )
              result = nil
              result_index = -1
              list.each_with_index do  |item,index|
                if item.match(/\(mswin32\)/)
                  result_index = index
                  result = item
                  break
                end
              end
              return [result, result_index]
            end
            
          end
          
          find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement )
        
        end
      end
    
    end
    # end patching of Gems.

   





    # Install multiple gems with dependencies and without documentation.
    # pass in an array of gem names to install.
    #
    #   install_gems "mongrel", "redcloth", "mongrel_service", "tzinfo", "capistrano"
    #
    #
    def install_gems(*attrs)
        attrs.flatten!
        puts attrs.inspect
        attrs.each do |gem_name|
            puts "installing gem #{gem_name}"
            Gem:: GemRunner.new.run(['install', gem_name, '--include-dependencies', '--no-rdoc', '--no-ri'])
        end
    
    end

    
if ARGV[0] == "help"
  puts "Install gems via this tool on Windows to skip selection of OS and version number.  This will install the gem with dependencies, without documentation."
  puts "Examples:"
  puts "   gem_install rails"
  puts "   gem_install rails mongrel mongrel_service"
  puts "Report bugs to info@napcs.com ."
else
    install_gems ARGV
end
    

__END__

:endofruby
"ruby" -x "%~f0" %*

That’s it. It works great so far and I’m going to be embedding this into the Eclipse for Rails installation. You Mac users should have no trouble taking this concept to your platform. It just involves changing the regex from mswin32 to ruby in the Ruby portion of the script and then copying the Ruby code into its own .rb file that you mark executable.

I should say something about how the whole thing works. When you run the batch file, it skips over the Ruby code because of the goto command and then actually passes itself to the Ruby interpreter.

   ruby -x gem_install.bat rails mongrel

The additional parameters you send to the batch file are also passed along to the Ruby script as well. The Ruby interpreter ignores everything in the file until it gets to the shebang line (#!/bin/ruby). It starts executing code from this point on, but then it stops when it gets to __END__. Everything else in the file is ignored.

This method is how we Windows users get to do things like rake or gem or even rails Linux users can just set a Ruby script to be executable. Windows users have a little more trouble doing that so we cheat.

Before I go, I should say that I could not have done this without Curt Hibbs, who uses this batch-file-to-ruby approach in the One-Click Ruby Installer. He’s a swell guy.

If you’ve got comments, I’d love to hear them. Let me know what you think, or if there are ways I can improve this.

Using Ruby Blocks to make custom helpers in Rails

Posted by Brian in Rails, snacks (July 2nd, 2007)

A ruby block is one of the most powerful things about the language. I think it’s one of the most important concepts in the entire language.

To illustrate the point, I’ll explain blocks a bit by using an example we can all relate to.

Have you ever written code that looks like this?

<% if @documents.size > 0 %>
   <% documents.each do |@document| %>
      

<%=@document.title %>

<% end %> <% else %>

There are no items to show.

<% end %>

You check to see if any results are in the collection so you can display a nice friendly message to people saying
that you didn’t find anything.

We can use Ruby’s blocks to shorten that code and make it a bit more generic.

What are blocks?

Methods in Ruby can take arguments (or parameters, if you prefer),
but they can also take blocks of code. Ruby can then execute this
code you pass in within the scope of the method. That sounds pretty abstract,
but it’s nothing more than just allowing your code to be wrapped by some other code.

Using a helper that accepts your code as a block, you can do something like this in one of your views:

<% display_items_from @documents, "There are no documents to display" do |document| %>
   

<%=link_to document, :action=>"show", :id=>document %>

<% end %>

We expect that this method will check the size of the @documents collection, and if it has results, it will loop over the collection of documents and then execute the block of code we pase in. If the collection of documents is empty, we will display the message.

The first iteration – Displaying the items

The code is actually really simple to implement. Let’s handle the easy case first… the case
where we actually have documents to show.

def display_items_from(collection, blank_message="There is nothing to display")
	if collection.size > 0
		collection.each do |item|
			yield item
		end
	end
end

This is where Ruby gets really abstract. That method declaration doesn’t mention anything about handling a block! It turns out that I can pass a block to any method, and Ruby will use it if I use the yield keyword. I don’t have to declare the block as a parameter in most cases.

When I do

	<% display_items_from @documents, "There are no documents to display" do |document|| %>
		

<%=link_to document.title, :action=>"show", :id=>document %>

<% end %>

in my view, I am passing document in as a local variable that the helper method can bind to.

Look at this section of code:

		collection.each do |item|
			yield item
		end

The yield statement is what does the actual output. I specified

		

<%=link_to document, :action=>"show", :id=>document %>

so this will execute the block of code I passed in for each item in the collection,
binding item with my local variable document and using yield to execute the block of code I passed.

To reiterate, you use blocks in Ruby to run a bunch of code in the context and scope of another method.

The other case – Nothing to return

Rails has a few wonderful helper methods we can use to make this helper more useful. Right now, you can get the same result by using a partial with the :collection parameter.
But what we really want is a nice, clean way to iterate over our collection if it has items, and display a friendly message to our users if there were no items in the collection.

You’d think that would be easy… I could just do

def display_items_from(collection, blank_message="There is nothing to display")

	if collection.size > 0
		collection.each do |item|
		   	yield item
		end
	else
		blank_message
	end

end

There’s a slight problem with that approach though… I will never see that blank message on the screen anywhere. Remember, this type of
helper method is invoked using the regular ERb evaluation mechanism of <% .. %> and not the ERb output
mechanism (<%= .. %>). There’s nothing in our code that will output blank_message variable.

Ok, so what if I did this?

  else
  	yield blank_message
  end

Would that work?

No, of course not. Yield operates on the stuff I passed in, so it’s going to try to call document.title and I’ll get an exception.

It turns out the solution requires the use of the concat method. The concat method takes two parameters: the string to output, and the block or proc to bind the output to.

	else
		concat(blank_message, block.binding)
	end

There’s just one small problem…. I’ll get an exception when I call this because
block was never defined. Remember when I said that you don’t have to declare
that you’re passing a block to a method in Ruby, and that it will ignore the block of code you pass if
the method doesn’t call for it? Well, that’s true, except in the case where you actually need to do
something with the block, like bind to it.

All I have to do is change the method declaration just slightly by adding
&block as the third parameter. The ampersand is what specifies the block,
and it absolutely must come last in the list of method arguments.

Our updated helper method now looks like this:

def display_items_from(collection, blank_message="There is nothing to display", &block)
	if collection.size > 0
		collection.each do |item|
		   	yield item
		end
	else
		concat(blank_message, block.binding)
	end
end

Handling Errors

We should always make sure that this method is called with a block. We can use the built-in method called block_given? which returns false if the developer
didn’t pass a block to the method.

    raise ArgumentError, "You need to provide a block." unless block_given?

You could make that more helpful by placing some nice instructions in that message.

Displaying a record count

If I wanted to display a count of the number of results you found in the output above the
code I passed, all I have to do is make
use of the concat method again, and place it above the yield statement.

		concat("

You have #{collection.size} items.

", block.binding) collection.each do |item| yield item end

Wrapping up

The final method looks like this:

def display_items_from(collection, blank_message="There is nothing to display", &block)

    raise ArgumentError, "You need to provide a block." unless block_given?

	if collection.size > 0
		concat("

You have #{collection.size} items.

", block.binding) collection.each do |item| yield item end else concat(blank_message, block.binding) end end

Now that I’ve covered this in more detail, the wheels should be spinning in your head. Think about
all of the possible helpers you could write to reduce your view code?

  • Easily make a navigation bar of links
  • Create a helper to assist with the creation of rounded-corner areas
  • Come up with a neat mechanism to show admin-only content

I hope this was helpful. Don’t hesitate to ask questions in the comments. If there’s anything I can make more clear,
let me know and I’ll do what I can. I was extremely influenced by Bruce Williams and Marcel Molina Jr.’s presentation at RailsConf. You can read the
slides at http://www.codefluency.com/assets/2007/5/18/VisForVexing.pdf