views:

78

answers:

1

I have a class which performs several database operations, and I want to write a unit test which verifies that these operations are all performed within a transaction. What's a nice clean way to do that?

Here's some sample code illustrating the class I'm testing:

class StructureUpdater
  def initialize(structure)
    @structure = structure
  end

  def update_structure
    SeAccount.transaction do
      delete_existing_statistics
      delete_existing_structure
      add_campaigns
      # ... etc
    end
  end

  private

  def delete_existing_statistics
    # ...
  end

  def delete_existing_structure
    # ...
  end

  def add_campaigns
    # ...
  end
end
A: 

Rspec lets you assert that data has changed in the scope of a particular block.

it "should delete existing statistics" do
    lambda do
        @structure_updater.update_structure
    end.should change(SeAccount, :count).by(3)
end

...or some such depending on what your schema looks like, etc. Not sure what exactly is going on in delete_existing_statistics so modify the change clause accordingly.

EDIT: Didn't understand the question at first, my apologies. You could try asserting the following to make sure these calls occur in a given order (again, using RSpec):

EDIT: You can't assert an expectation against a transaction in a test that has expectations for calls within that transaction. The closest I could come up with off the cuff was:

describe StructureUpdater do
    before(:each) do
        @structure_updater = StructureUpdater.new(Structure.new)
    end

    it "should update the model within a Transaction" do
        SeAccount.should_receive(:transaction)
        @structure_updater.update_structure
    end

    it "should do these other things" do
        @structure_updater.should_receive(:delete_existing_statistics).ordered
        @structure_updater.should_receive(:delete_existing_structure).ordered
        @structure_updater.should_receive(:add_campaigns).ordered
        @structure_updater.update_structure
    end
end

ONE MORE TRY: Another minor hack would be to force one of the later method calls in the transaction block to raise, and assert that nothing has changed in the DB. For instance, assuming Statistic is a model, and delete_existing_statistics would change the count of Statistic in the DB, you could know that call occurred in a transaction if an exception thrown later in the transaction rolled back that change. Something like:

it "should happen in a transaction" do 
    @structure_updater.stub!(:add_campaigns).and_raise
    lambda {@structure_updater.update_structure}.should_not change(Statistic, :count) 
end
Dave Sims
I'm not sure how that will help verify that the operations were performed inside a transaction
Pete Hodgson
I think I understand now -- see edit.
Dave Sims
Sorry for the misunderstanding. I assumed you meant you wanted to be sure *these calls* occurred within the transaction. You meant you wanted to be sure these calls occurred *within a transaction*.
Dave Sims
Thanks Dave, this is closer to what I was looking for. What I really need (and the motivation for the question) is a way to assert that these three methods are all called *within* the transaction do block.
Pete Hodgson
Yeah the problem with my approach is that setting the expectation on "transaction" hides transaction functionality and the method calls within the transaction aren't passed in with the block. Otherwise it would have worked. : ) It's actually a good problem, I'll think on it some more.
Dave Sims