delphiexceptiondelphi-7dunit

Delphi 7 Dunit checks after StopExpectingException are not working as I expect


The below code works fine, the calc... generates an exception, comment it out or change calc... to not throw and exception and the test fails.

  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');

My problem is that any checks I put in this test method after this do not execute.
so

  checkEquals(1,0);
  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');

fails on the 1st checkEquals

  StartExpectingException(exception);
  calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
  StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
  checkEquals(1,0);

passes - why?

I have tried to work out what version of Dunit I am using:

testframework.pas has the following - which didn't seem to 
rcs_id: string = '#(@)$Id: TestFramework.pas,v 1.117 2006/07/19 02:45:55
rcs_version : string = '$Revision: 1.117 $';
versioninfo.inc
ReleaseNo : array[1..3] of Integer
          = (9,2,1);
ReleaseStr     = '9.2.1';
ReleaseWhen : array[1..6] of Integer
          = (2005,09,25,17,30,00);

Solution

  • StopExpectingException cannot work the way you expect. It's important to understand the flow of execution in an exception state to see why.

    Consider the following code:

    procedure InnerStep(ARaiseException);
    begin
      Writeln('Begin');
      if ARaiseException then
        raise Exception.Create('Watch what happens now');
      Writeln('End');
    end;
    
    procedure OuterStep;
    begin
      try
        InnerStep(False); //1
        InnerStep(True);  //2
        InnerStep(False); //3
      except
        //Do something because of exception
        raise;
      end;
    end;
    

    When you call OuterStep above, line //2 will raise an exception inside InnerStep. Now whenever an exception is raised:

    So when you write:

    StartExpectingException(...);
    DoSomething();
    StopExpectingException(...);
    

    There are 2 possibilities:

    1. DoSomething raises an exception and StopExpectingException is never called.
    2. DoSomething doesn't raise an exception and when StopExpectingException is called there is no exception.

    David has explained that the DUnit framework calls StopExpectingException for you. But you may be wondering how to approach your test case checking multiple exception scenarios.

    Option 1

    Write smaller tests.
    You know that's what everyone says you're supposed to do in any case right? :)
    E.g.

    procedure MyTests.TestBadCase1;
    begin
      ExpectedException := ESomethingBadHappened;
      DoSomething('Bad1');
      //Nothing to do. Exception should be raised, so any more code would
      //be pointless.
      //If exception is NOT raised, test will exit 'normally', and
      //framework will fail the test when it detects that the expected
      //exception was not raised.
    end;
    
    procedure MyTests.TestBadCase2;
    begin
      ExpectedException := ESomethingBadHappened;
      DoSomething('Bad2');
    end;
    
    procedure MyTests.TestGoodCase;
    begin
      DoSomething('Good');
      //Good case does not (or should not) raise an exception.
      //So now you can check results or expected state change.
    end;
    

    Option 2

    As David has suggested, you can write your own exception handling inside your test. But you'll note that it can get a little messy, and you'll probably prefer option 1 in most cases. Especially when you have the added benefit that distinctly named tests make it easier to identify exactly what went wrong.

    procedure MyTests.TestMultipleBadCasesInTheSameTest;
    begin
      try
        DoSomething('Bad1');
        //This time, although you're expecting an exception and lines
        //here shouldn't be executed:
        //**You've taken on the responsibility** of checking that an
        //exception is raised. So **if** the next line is called, the
        //expected exception **DID NOT HAPPEN**!
        Fail('Expected exception for case 1 not raised');
      except
        //Swallow the expected exception only!
        on ESomethingBadHappened do;
        //One of the few times doing nothing and simply swallowing an
        //exception is the right thing to do.
        //NOTE: Any other exception will escape the test and be reported
        //as an error by DUnit
      end;
    
      try    
        DoSomething('Bad2');
        Fail('Expected exception for case 2 not raised');
      except
        on E: ESomethingBadHappened do
          CheckEquals('ExpectedErrorMessage', E.Message);
          //One advantage of the manual checking is that you can check
          //specific attributes of the exception object.
          //You could also check objects used in the DoSomething method
          //e.g. to ensure state is rolled back correctly as a result of
          //the error.
      end;
    end;
    

    NB! NB! Something very important to note in option 2. You need to be careful about what exception class you swallow. DUnit's Fail() method raises an ETestFailure exception to report to the framework that the test failed. And you wouldn't want to accidentally swallow the exception that's going to trigger the test failure for expected exception.

    The subtle issues related exception testing make it important to: test first, ensure you have the correct failure, and only then implement the production code change to get a pass. The process will significantly reduce the chances of a dud test.