30

I'm wondering what's the best practice to test for exceptions in dunit. I am not very familiar with method pointers in Delphi. Is there any possibility to bind arguments to a method pointer so that it could be invoked without arguments. At the moment I always write an additional method which does this 'binding' manually. This is going to be annoying if the SUT has a lot of throwing methods.

// What i did before i knew abput CheckExcepion
procedure MyTest.MyMethod_BadInput_Throws;
var
    res: Boolean;
begin
    res := false;
    try
        sut.MyMethod('this is bad');
    except
        on e : MyExpectedException do:
            res := true;
    end;
    CheckTrue(res);
end;

// What i do now
procedure MyTest.MyMethodWithBadInput;
begin
    sut.MyMethod('this is bad');
end;

procedure MyTest.MyMethod_BadInput_Throws;
begin
    CheckException(MyMethodWithBadInput, MyExpectedException);
end;

// this would be nice
procedure MyTest.MyMethod_BadInput_Throws;
begin
    CheckException(
        BindArguments(sut.MyMethod, 'this is bad'),  // <-- how to do this
        MyExpectedException);
end;
nfc1
  • 190
  • 1
  • 1
  • 11
hansmaad
  • 18,417
  • 9
  • 53
  • 94

5 Answers5

46

You can use StartExpectingException to surround your your method call).

StartExpectingException(MyException);
MyMethod(MyParam);
StopExpectingException();
TridenT
  • 4,879
  • 1
  • 32
  • 56
  • 1
    +1 this looks like the *right* way to do this; thank you for teaching me something new! – David Heffernan Jan 06 '11 at 10:46
  • 4
    Or even better, just set the ExpectedException property inside your test. – Erick Sasse Jan 06 '11 at 13:58
  • 1
    Maybe add a try...finally here? – Jeroen Wiert Pluimers Jan 06 '11 at 19:09
  • 4
    @Jeroen: No don't add a try...finally. It will in fact cause your test to fail incorrectly even when an exception is raised. `StopExpectingException` is implemented as: _I'm not supposed to be called; if I am, then an exception wasn't raised, so fail the test._ Putting it in a `finally` just forces it to fail the test. Strictly speaking, you don't need `StopExpectinException` at all. If the test method ends normally while in `ExpectedException` state, the test fails. – Disillusioned Apr 01 '11 at 14:12
  • 3
    See that the MyMethod will raise an exception, and nothing after it will execute. If you have more than one check, they won't be executed. – neves Oct 27 '11 at 12:38
3

I don't know if DUnit supports it yet, but this is a perfect use case for anonymous methods which were introduced in Delphi 2010. If DUnit doesn't support it then you can easily modify the source yourself.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Sounds good, but how do i define a anonymous method? `CheckException` takes a `TTestMethod = procedure of object`. My first try `CheckException(procedure begin raise Exception.Create('Error'); end, Exception);` Does not compile (incompatible types-> regular proc, method ptr) – hansmaad Jan 06 '11 at 10:15
  • @hansmaad You'd need to define the anonymous procedure as a variable in your test function. And you'd need to add a CheckException to TTestCase that had `reference to procedure` as its parameter rather than `procedure of object`. However, the answer of @TridenT to use StartExpectingException appears to be the right way to do this. – David Heffernan Jan 06 '11 at 10:31
  • This is exactly what i did right know and it works. One have to overload the `CheckException` method taking a `TTestProc` instead of `TTestMethod`. Then you can use ananoymous methods. But StartExpectingException seems to be the way to go in dunit, even if i don't like it that i have to call the Stop.. method. – hansmaad Jan 06 '11 at 10:43
  • 1
    @hansmaad Be aware, that the code after `StopExpectingException` wont execute (at least in Delphi 2006). – Alois Heimer Apr 21 '14 at 08:16
3

As noted, this is a great place for anonymous methods.

Here's how I do it. I "borrowed" this from Alex Ciobanu:

procedure TestTMyClass.CheckException(aExceptionType: TClassOfException; aCode: TTestCode; const aMessage: String);
var
  WasException: Boolean;
begin
  WasException := False;
  try
    aCode;
  except
    on E: Exception do
    begin
      if E is aExceptionType then
      begin
        WasException := True;
      end;
    end;
  end;
  Check(WasException, aMessage);
end;

Then call it with something like:

CheckException(ETestingException, 
             procedure begin FMyClass.RaiseTestingException end,      
             'The ETestingException exception didn''t get raised.  That is impossible!');
Nick Hodges
  • 16,902
  • 11
  • 68
  • 130
  • I made a working version of this method and posted it below. Thanks a lot for posting this, it's very useful to me. – DBedrenko Mar 17 '16 at 11:39
2

To use StartExpectingException() is not the best way in case you want to test more then one Exception cases. In order to test all possible cases in my Test procedure, together with exceptions I use this algorithm:

uses
  Dialogs;
procedure MyTest.MyMethod_Test;
begin
  // Test for Exceptions
  try
    MyMethod(MyParam1CreatingException1);
    ShowMessage('Error! There should have been exception: Exxx here!');
    Check(false);
  except on E: Exception do Check(E is  ExceptionType1); end; // This exception is OK
  try
    MyMethod(MyParam2CreatingException2);
    ShowMessage('Error! There should have been exception: Exxx here!');
    Check(false);
  except on E: Exception do Check(E is  ExceptionType2); end; // This exception is OK
  // ... test other exceptions ...

  // Test other parameters
  CheckEquals('result1', MyMethod(MyParam1));
  CheckEquals('result2', MyMethod(MyParam2));
  // ... other tests ...
end;

The reason why I use ShowMessage('Error! There should be exception: Exxx here!'); instead of the provided Check(false, 'There should have been an EListError.'); method is that in my case (Delphi6) the Check(boolean, 'Message') does not work - it does not show the message in case that Check is inside try...except block (do not know why).

nfc1
  • 190
  • 1
  • 1
  • 11
0

This is the working and improved version of Nick Hodges' answer, which sub-classes DUnit's TestFramework.TTestCase:

uses
  TestFramework, System.SysUtils;
type
  TTestCode = reference to procedure;

  TTestCasePlus = class(TestFramework.TTestCase)
    procedure CheckException(
      ExceptionType: TClass; Code: TTestCode; const Message: String = '');
  end;

implementation

procedure TTestCasePlus.CheckException(
  ExceptionType: TClass; Code: TTestCode; const Message: String = '');
{ Check whether some code raises a specific exception type.

Adapted from http://stackoverflow.com/a/5615560/797744

Example:

  Self.CheckException(EConvertError,
                      procedure begin UnformatTimestamp('invalidstr') end);

@param ExceptionType: The exception class which we check if it was raised.
@param Code: Code in the form of an anonymous method that should raise the
  exception.
@param Message: Output message on check failure. }
var
  WasRaised: Boolean;
begin
  WasRaised := False;
  try
    Code;
  except
    on E: Exception do
      if E is ExceptionType then
        WasRaised := True;
  end;
  Check(WasRaised, Message);
end;

A nice bonus to this method of checking if an exception was raised over Start/StopExpectingException() is that you can run the testrunner in the debug build and it won't keep bothering you with "Exception was raised. Break? Continue?" every time an exception is raised--even though it was handled.

DBedrenko
  • 4,871
  • 4
  • 38
  • 73