-
Notifications
You must be signed in to change notification settings - Fork 1
Testing
Marco Breveglieri edited this page Nov 24, 2025
·
1 revision
Guide to testing code that uses Murphy policies, including unit testing strategies and test mode.
Murphy includes a global test mode flag that disables actual delays, making tests run instantly.
uses Murphy.Globals;
procedure TMyTests.SetUp;
begin
MurphyTestModeEnabled := True;
end;
procedure TMyTests.TearDown;
begin
MurphyTestModeEnabled := False;
end;-
Retry policy:
Wait()delays are skipped -
Circuit breaker:
Within()duration checks use real time, but operations complete instantly - Rate limit: Operations complete instantly (but rate limiting still enforces limits)
procedure TRetryTests.TestRetryExecutesMultipleTimes;
var
Policy: IRetryPolicy;
AttemptCount: Integer;
begin
MurphyTestModeEnabled := True;
AttemptCount := 0;
Policy := TRetryBuilder
.Handle(Exception)
.Retry(3)
.Build;
try
Policy.Execute(
procedure
begin
Inc(AttemptCount);
if AttemptCount < 3 then
raise Exception.Create('Simulated failure');
// Third attempt succeeds
end);
Assert.AreEqual(3, AttemptCount, 'Should have attempted 3 times');
finally
MurphyTestModeEnabled := False;
end;
end;procedure TCircuitBreakerTests.TestCircuitOpensAfterThreshold;
var
Policy: ICircuitBreakerPolicy;
I: Integer;
begin
MurphyTestModeEnabled := True;
Policy := TCircuitBreakerBuilder
.Handle(Exception)
.Fail(3)
.Build;
// Trigger 3 failures to open circuit
for I := 1 to 3 do
begin
try
Policy.Execute(procedure begin raise Exception.Create('Test'); end);
except
// Expected
end;
end;
Assert.AreEqual(TCircuitState.Open, Policy.CircuitState);
MurphyTestModeEnabled := False;
end;procedure TFallbackTests.TestFallbackReturnsDefaultOnException;
var
Policy: IFallbackPolicy<string>;
Result: string;
begin
Policy := TFallbackBuilder<string>
.Handle(Exception)
.Fallback(function: string begin Result := 'Fallback Value'; end)
.Build;
Result := Policy.Execute(
function: string
begin
raise Exception.Create('Test error');
end);
Assert.AreEqual('Fallback Value', Result);
end;procedure TRateLimitTests.TestRateLimitRejectsExcessCalls;
var
Policy: IRateLimitPolicy;
I: Integer;
RejectedCount: Integer;
begin
MurphyTestModeEnabled := True;
RejectedCount := 0;
Policy := TRateLimitBuilder
.Handle(Exception)
.Allow(5)
.Within(TTimeSpan.FromSeconds(1))
.Build;
// Try 10 calls, expect 5 rejections
for I := 1 to 10 do
begin
try
Policy.Execute(procedure begin {do nothing} end);
except
on E: ERateLimitRejectedException do
Inc(RejectedCount);
end;
end;
Assert.AreEqual(5, RejectedCount);
MurphyTestModeEnabled := False;
end;You can mock policy interfaces for testing code that depends on them:
type
TMockRetryPolicy = class(TInterfacedObject, IRetryPolicy)
public
ExecuteCount: Integer;
procedure Execute(AProc: TProc);
// ... other interface methods
end;
procedure TMockRetryPolicy.Execute(AProc: TProc);
begin
Inc(ExecuteCount);
AProc; // Always succeeds
end;
procedure TServiceTests.TestServiceUsesRetry;
var
MockPolicy: TMockRetryPolicy;
Service: TMyService;
begin
MockPolicy := TMockRetryPolicy.Create;
Service := TMyService.Create(MockPolicy);
Service.PerformOperation;
Assert.IsTrue(MockPolicy.ExecuteCount > 0, 'Should have used retry policy');
end;procedure TestRetryWithFallback;
var
Retry: IRetryPolicy;
Fallback: IFallbackPolicy<string>;
AttemptCount: Integer;
Result: string;
begin
MurphyTestModeEnabled := True;
AttemptCount := 0;
Retry := TRetryBuilder.Handle(Exception).Retry(2).Build;
Fallback := TFallbackBuilder<string>
.Handle(Exception)
.Fallback(function: string begin Result := 'Fallback'; end)
.Build;
Result := Fallback.Execute(
function: string
begin
Retry.Execute(
procedure
begin
Inc(AttemptCount);
raise Exception.Create('Always fails');
end);
end);
Assert.AreEqual(3, AttemptCount, 'Should retry 2 times (3 total attempts)');
Assert.AreEqual('Fallback', Result, 'Should return fallback value');
MurphyTestModeEnabled := False;
end;- Always use test mode - Tests will run much faster
- Test failure scenarios - Not just happy paths
- Verify attempt counts - Ensure retry logic works
- Test state transitions - For circuit breaker
- Clean up test mode - Reset in TearDown to avoid affecting other tests
- Use DUnitX/DUnit - Standard Delphi testing frameworks work well
- Test policy combinations - Ensure they interact correctly
For integration tests (testing with real delays):
procedure TIntegrationTests.TestRetryWithRealDelay;
var
Policy: IRetryPolicy;
StartTime, EndTime: TDateTime;
begin
MurphyTestModeEnabled := False; // Use real delays
Policy := TRetryBuilder
.Handle(Exception)
.Retry(2)
.Wait(TTimeSpan.FromMilliseconds(100))
.Build;
StartTime := Now;
try
Policy.Execute(
procedure
begin
raise Exception.Create('Always fails');
end);
except
// Expected
end;
EndTime := Now;
var ElapsedMs := MilliSecondsBetween(EndTime, StartTime);
Assert.IsTrue(ElapsedMs >= 200, 'Should have waited at least 200ms');
end;unit Murphy.Tests.Retry;
interface
uses
DUnitX.TestFramework,
Murphy.Policy.Retry,
Murphy.Globals;
type
[TestFixture]
TRetryPolicyTests = class
public
[Setup]
procedure SetUp;
[TearDown]
procedure TearDown;
[Test]
procedure TestSuccessOnFirstAttempt;
[Test]
procedure TestRetryUntilSuccess;
[Test]
procedure TestExceptionRaisedAfterRetriesExhausted;
[Test]
procedure TestUnhandledExceptionNotRetried;
end;
implementation
procedure TRetryPolicyTests.SetUp;
begin
MurphyTestModeEnabled := True;
end;
procedure TRetryPolicyTests.TearDown;
begin
MurphyTestModeEnabled := False;
end;
procedure TRetryPolicyTests.TestSuccessOnFirstAttempt;
var
Policy: IRetryPolicy;
Executed: Boolean;
begin
Policy := TRetryBuilder.Handle(Exception).Retry(3).Build;
Executed := False;
Policy.Execute(procedure begin Executed := True; end);
Assert.IsTrue(Executed);
end;
procedure TRetryPolicyTests.TestRetryUntilSuccess;
var
Policy: IRetryPolicy;
Attempts: Integer;
begin
Policy := TRetryBuilder.Handle(Exception).Retry(3).Build;
Attempts := 0;
Policy.Execute(
procedure
begin
Inc(Attempts);
if Attempts < 3 then
raise Exception.Create('Fail');
end);
Assert.AreEqual(3, Attempts);
end;
procedure TRetryPolicyTests.TestExceptionRaisedAfterRetriesExhausted;
var
Policy: IRetryPolicy;
begin
Policy := TRetryBuilder.Handle(Exception).Retry(2).Build;
Assert.WillRaise(
procedure
begin
Policy.Execute(procedure begin raise Exception.Create('Always fails'); end);
end,
Exception);
end;
procedure TRetryPolicyTests.TestUnhandledExceptionNotRetried;
var
Policy: IRetryPolicy;
Attempts: Integer;
begin
Policy := TRetryBuilder.Handle(EIdHTTPProtocolException).Retry(3).Build;
Attempts := 0;
Assert.WillRaise(
procedure
begin
Policy.Execute(
procedure
begin
Inc(Attempts);
raise EDatabaseError.Create('Not handled');
end);
end,
EDatabaseError);
Assert.AreEqual(1, Attempts, 'Should not retry unhandled exception');
end;
end.