Topic: Trying to stub out db calls in model rspec.

Hi all,

Trying to stub out the save method in a model.

The background is thus, model, with before_create to set up some default values, key etc...

MODEL
Class Site < ActiveRecord::Base

before_create :set_defaults

private
  def set_defaults
     self.key = Digest::SHA256.hexdigest("--#{Time.now.to_i}--#{rand(100000)}--#{self.url}--")
  end



RSPEC
describe Site, " a new site record" do
  include SiteSpecHelper
  setup do
     Digest::SHA256.stub!(:hexdigest).and_return('123456789')
     
     @site = Site.new
     @site.attributes = valid_site_attributes
     @site.stub!(:save).and_return(:true)
     @site.save
       
  end
 
  it "should create the key from time and url" do
    @site.key.should eql('123456789')
  end
end


When I have @site.stub!(:save).and_return(:true) uncommented, i.e. to stub out the save, I get a nil error back on the test.

If I comment out the @site.stub!..., it passes and writes to the test db.

I've also tried Site.stub!(:save).and_return(@site), but that still writes to the database.

Being the end of a long day I know I'm missing something obvious; alas I can't spot it, any ideas folks?

message from rspec:

'Site a new site record should create the key from time and url' FAILED
expected "123456789", got nil (using .eql?)

Last edited by colindensem (2007-10-24 12:14:51)

Re: Trying to stub out db calls in model rspec.

You are returning :true from your stub! Did you perhaps mean:

@site.stub!(:save).and_return(true)

Re: Trying to stub out db calls in model rspec.

Oh... interesting. You are using a before_filter to stuff the crypto into your key. It is very possible that because you have stubbed out save, the part of Rails that invokes create for new objects and update for changed (existing) ones is not run. It is also possible that your before_filter won't trigger. You might put a logger.debug in the before_filter to see if it's being executed.

How's about this:

Digest::SHA256.expects(:hexdigest).once.and_returns('123456') # this is an expectation that you're looking at the hex key

Re: Trying to stub out db calls in model rspec.

cwd,

Yes, sorry I'd been trying a few things and when I came to post here I'd left that :true in there by accident.

I'll try the logger entry and your expectation smells better than the stub, but both achieve a similar net result given that specific test example?

Is the 'interesting' that I'm dong crypto in a before_filter good or bad, I always worry about best practice in a young knowledge base such as my own.

Having spent a few more hours on other tests I'm nearly at a loss as to how to get totally away from fixtures for model testing. Basic ? methods I'm ok with, but more complex find with conditions=>[...] are burning me to create records. It's all good learning though!

Thanks for reading,
Colin.

Last edited by colindensem (2007-10-26 05:31:33)

Re: Trying to stub out db calls in model rspec.

The interesting is neither good nor bad. I wouldn't have done it because it appears only to affect create and I believe you could just move that call to hexdigest into create to achieve the same result, leaving only one place to look for all the relevant code.

I can't speak to best practices in this case, but only to how I would have written the code.

For model testing, it is unlikely you will move completely away from fixtures because you are really trying to find out whether your models interact in the round-trip to the database. I find mocks and stubs most valuable in two cases. The first is dangerous, time-consuming, or non-repeatable cases. Your random generation of a hex key is a non-repeatable case that you've made repeatable using a mock. Other examples would be connecting to PayPal to charge a credit card or something like that.

For controller and view testing, mocks and stubs rock. You really can tell what's going on and fast. Just be aware that fixtures for your models can be brittle and cause some test maintenance issues. Check out some of the better fixtures plugins.

Re: Trying to stub out db calls in model rspec.

I sat down on Friday and looked at this again. I've now moved the code to the logical before_create method.

For testing, I'm now invoking the before create method and testing what I expect to happen. So no more calling save, I know the before_create will be called so lets test what happens in that method, either by way of expectations or passing values and checking the result. It also takes care of the db trip.

cwd, thank you for your time on this, I'll look at some fixture plugins, I've got some models with interlinked dependency's, that should keep me busy learning again.