CheckException only accepts 0-parameter methods; how do I test that other methods throw exceptions?
-
26-09-2019 - |
Question
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;
Solution
You can use StartExpectingException
to surround your your method call).
StartExpectingException(MyException);
MyMethod(MyParam);
StopExpectingException();
OTHER TIPS
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.
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!');
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).
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.