Calling (invoking) rails rake tasks from within ruby, for testing, try 2 3

Posted by Tim Connor Thu, 21 Feb 2008 21:48:00 GMT

Yesterday I wrote a post on capturing the output of rake from a ruby call using backticks, because I wanted to do so in a test. Well, I had Date.today stubbed out, but got lazy and used yesterday’s date, so of course when I come in today the test fails. In the light of a new day my problem was obvious. If you call rake from a sub-shell, say via ``, of course your mocking will not exist in that process. So today, I had Another Wonderful Opportunity for Learning (there are acronyms for this that use a different word than wonderful): a chance to do it right.

It took a little digging to figure out how to get this to work right with the built-in rails tasks, so here you go:


require ‘rake’
require ‘rake/rdoctask’
require ‘rake/testtask’
require ‘tasks/rails’

def capture_stdout
s = StringIO.new
oldstdout = $stdout
$stdout = s
yield
s.string
ensure
$stdout = oldstdout
end

Rake.application.rake_require ‘../../lib/tasks/metric_fetcher’
results = capture_stdout {Rake.application[‘metric_fetcher’].invoke}

I liberated capture_stdout from the rake tests themselves. In retrospect, I should have just looked there first. As TDD and especially BDD get even more widespread the tests are often the easiest place to look for good examples of usage.

Capturing a system call in ruby with backticks, while setting an environment variable 1

Posted by Tim Connor Wed, 20 Feb 2008 18:26:00 GMT

As you may know system() and `` both make system calls in ruby, but the second one captures the STDOUT for you, essentially, which is handy sometimes, like when testing. They don’t necessarily both do this by just a straight call to the commandline, but various internal utilities that make such thing possible in Windows too, for instance.

Well the problem with this is it can mess with your setting of an ENV as a precursor to your command. For instance, this is valid on my platform:


RAILS_ENV=test rake metric_fetcher

and works via system():

system(“RAILS_ENV=test rake metric_fetcher”)

but chokes in backticks, probably due to the aforementioned jiggering around for Windows compat:

`RAILS_ENV=test rake metric_fetcher`

Now you generally can just move the command back to the beginning of the line:

`rake RAILS_ENV=test metric_fetcher`

but if for some bizarre reason that doesn’t work for you (it’s probably something else going, wrong, though, to be honest) you can do this:

ENV[‘RAILS_ENV’]=‘test’; `rake metric_fetcher`

So ultimately, I can do this hacky wonder:


assert `rake RAILS_ENV=test metric_fetcher`.empty?

if my rake task succeeds silently and fails noisily (which is a useful characteristic for cron’ed tasks).

Thanks to Defiler and ocotpod for proving to me I really did have my head up my ass and that all but the leading env var in backticks form should work as expected.(fixture set-up issues as usual).

Addendum And if you are calling rake like this in a test and getting odd problems – consider turning transaction fixtures off.

Isolated controller and view testing in merb 2

Posted by Tim Connor Mon, 17 Dec 2007 08:41:00 GMT

As part of learning rspec for blerb, I’m working on better mocking for truer isolation tests. This includes true unit tests for models, controllers, and views, unlike the rails/testunit standard approach. This involves, primarily, mocking out the models and turning rendering off for the controller testing, and figuring out how to test just the rendering without the rest of the stack, for the views. I won’t claim any of this is well organized, or finalized, as we are just figuring out how to do some of it in the first place, but feel free to crib any of this you like off the git-web for blerb.

First a controller


describe “Articles Controller”, “index action” do
before(:each) do
article1 = mock("article") @article2 = mock("article") @articles = [article1, article2] Article.should_receive(:all).and_return(articles)
@controller = Articles.build(fake_request)
@controller.stub!(:render)
@controller.dispatch(‘index’)
end

it “should get successfully” do @controller.status.should == 200 end it “should assign all articles” do controller.instance_variable_get("articles").should == @articles end

end

The one non-intuitive part (for me), was making sure to stub render – otherwise you’ll be calling the view, and getting errors on your mocks because the view is calling methods you didn’t mock/stub out. Thus, it is key to isolate the controller from the model and the view if you want to easily test it, in this style. Oh yeah, and knowing about fake_request as part of the merb testing help.

The slightly trickier part was doing the same for the views. I dug through the merb source, and got most of the way, but hadn’t figured out how to pass in assigned instance variables by creating a dummy ViewContext, when ezra gave me a hand. I would NOT (and will not long term) do it exactly like this, but rather extract some sort of spec_helper to wrap the fake view_context creation and template engine calls, but it’s useful as is for an illustrative example.

class FakeArticleController
  def initialize(articles)
    @articles = articles
  end
end

describe "/articles" do
  before(:each) do
    @article1 = mock("article")
    @article2 = mock("article")
    @article1.should_receive(:title).and_return("Article 1")
    @article2.should_receive(:title).and_return("Article 2")    
    Article.stub!(:all).and_return([@article1,@article2])

    @template = "#{MERB_VIEW_ROOT}/articles/index.html.erb"
    @engine = Merb::Template.engine_for(@template)
    @fake = Merb::ViewContext.new FakeArticleController.new(Article.all)
    @body = @engine.transform(:file => @template, :view_context => @fake)
  end
  
  it "should list all articles" do
    @body.should include("<li>Article 1</li>")
    @body.should include("<li>Article 2</li>")
  end
end

Using define_method to overwrite a class' initialize as an example of overwriting a method from your mock, version 2 1

Posted by Tim Connor Fri, 30 Nov 2007 22:40:00 GMT

Last year, I wrote an article about redefining an instance method from a module via alias_method. I understand better how extend/include work together now (and the ClassMethods include patterns and whatnot) so I am little embarrassed at my earlier confusion, but there is one concrete thing I wanted to add/simplify.

If you are mocking and don’t need to keep a reference to the old method, using define_method might be marginally cleaner.

def self.included(associator_class) 
  associator_class.send(:define_method, :initialize, instance_method(:initialize))
end

This is handier because you can skip the method definition/reference logic and just do it in situ, for instant-stubification


ClassToBeIntercepted.send :define_method, :method_to_be_nuked, proc {return ’nuked’}