How to spec transaction in Controller?

Hi,

in my Controller I have some tansactions. I’m trying to write specs,
which test the two program pathes (an exception is thrown or no
exception is thrown). But the test seems always to go into the path,
where the exception is catched.

Here’s an example for the delete action
(destroy method is overwritten, so :dependent => :destroy is not
possible here)

The test case “should flash the notice” fails, because not the program
path of success is executed, but the rescue path is executed.

Perhaps my exception handling is generally wrong ?

Thanks
Andrea

plannings_controller.rb

def destroy
@planning = Physical::Planning::Planning.find(params[:id])

respond_to do |format|
begin
ActiveRecord::Base.transaction do
@planning.destroy
@planning.planned_amounts.each { |planned_amount|
planned_amount.destroy }
end

success

flash[:notice] = “The
#{Physical::Planning::Planning::DISPLAY_NAME.downcase} '” +
@planning.title + “’ was successfully deleted.”
format.html { redirect_to(planning_plannings_url) }
format.xml { head :ok }
rescue => e

DB raised errors

flash[:error] = “The
#{Physical::Planning::Planning::DISPLAY_NAME.downcase} '” +
(@planning.nil? ? “” : @planning.title) + “’ could not be deleted.
Error:” + e
format.html { redirect_to(planning_plannings_url) }
format.xml { render :xml => @planning.errors, :status =>
:unprocessable_entity }
end
end
end

plannings_controller_spec.rb

describe Physical::Planning::PlanningsController, “DELETE” do
include PlanningMacros

should_require_login :put, :delete
should_require_authorization :put, :delete

context “when the user is logged in” do
before(:each) do
do_login
do_authorization

@planning = mock_model(Physical::Planning::Planning,
valid_planning_attributes)
@planned_amount = mock_model(Physical::Planning::PlannedAmount,
valid_planned_amount_attributes)

@planning.stub!(:destroy)
@planning.stub!(:planned_amounts).and_return([@planned_amount])

Physical::Planning::Planning.stub!(:find).and_return(@planning)

@planning.errors.stub!(:full_messages).and_return([])
end

delete successfully

-------------------

context “and the Planning deletes successfully” do

it “should destroy the Physical::Planning::Planning in a transaction”
do
Physical::Planning::Planning.should_receive(:transaction).any_number_of_times.and_yield(
@planning.should_receive(:destroy)
)
do_put_delete
end

it “should destroy the PlannedAmounts of the
Physical::Planning::Planning in a transaction” do
Physical::Planning::Planning.should_receive(:transaction).any_number_of_times.and_yield(
@planned_amount.should_receive(:destroy)
)
do_put_delete
end

it “should flash the notice” do
do_put_delete
flash[:notice].should match(/was successfully deleted/)
end

end

delete fails

------------

context “and the Planning fails to delete” do

it “should flash error message because of database exception” do
Physical::Planning::Planning.should_receive(:transaction).any_number_of_times.and_yield(
@planning.should_receive(:destroy).and_raise(ActiveRecord::RecordInvalid.new(@planning))
)
do_put_delete
flash[:error].should match(/could not be deleted/)
end
end
end
end

Neu: WEB.DE Doppel-FLAT mit Internet-Flatrate + Telefon-Flatrate
für nur 19,99 Euro/mtl.!* WEB.DE Produkte Übersicht: Apps, Browser, MailCheck & Co.
[WEB.DE Produkte Übersicht: Apps, Browser, MailCheck & Co.]

On Fri, Sep 25, 2009 at 3:06 AM, Andrea J. [email protected] wrote:

Hi,

in my Controller I have some tansactions. I’m trying to write specs, which test the two program pathes (an exception is thrown or no exception is thrown). But the test seems always to go into the path, where the exception is catched.

Here’s an example for the delete action
(destroy method is overwritten, so :dependent => :destroy is not possible here)

Off topic, but ActiveRecord offers a number of ways to interact with
model and transaction life cycles. The fact that you are overriding
destroy rather than hooking into the destroy cycle means you’re
bypassing a lot of functionality that you now have to implement
yourself (hence the problem you are describing in this post). If there
is any way for you to use those hooks instead, I’d strongly recommend
it.

  def destroy
    @planning = Physical::Planning::Planning.find(params[:id])

    respond_to do |format|
      begin
        ActiveRecord::Base.transaction do
          @planning.destroy
          @planning.planned_amounts.each { |planned_amount| planned_amount.destroy }
        end

Even if you’ve overridden destroy, why not just include this code in
the custom destroy method? Then the controller could just call
@planning.destroy and that method can worry about transactions.

    end

should_require_login :put, :delete
@planning.stub!(:destroy)
context “and the Planning deletes successfully” do

  ....

  it "should destroy the Physical::Planning::Planning in a transaction" do
    Physical::Planning::Planning.should_receive(:transaction).any_number_of_times.and_yield(
      @planning.should_receive(:destroy)
    )

This ^^ isn’t doing anything. any_number_of_times includes 0, which is
the number of times Planning (the class object) receives transaction
(the controller calls ActiveRecord::Base.transaction). I’d recommend
leaving out any_number_of_times. By default, should_receive expects
exactly one call, which is the right number in this case. And it
should be on ActiveRecord::Base, given the current state of the
implementation.

This, however, is a great example of how writing examples first can
have a positive impact on design. If I were writing this, I never
would have specified transactions in a controller action. This example
would simply be:

it “should destroy the Physical::Planning::Planning in a transaction” do
@planning.should_receive(:destroy)
do_put_delete
end

Then the implementation would only need call @planning.destroy to pass
the example. The transactional nature of the destroy method would be
specified in the model spec, and now any other code that gets written
later that needs to destroy a Planning instance can just send it
destroy() and you won’t have to duplicate this transaction code in
each of those cases.

HTH,
David