Unit Testing¶
This document describes how to set up, run, and develop unit tests for Bold for Delphi.
Overview¶
Bold for Delphi uses the DUnitX testing framework. Tests are located in the UnitTest/ folder:
UnitTest/
├── UnitTest.dpr # Main test project
├── UnitTest.dproj
├── run_coverage.ps1 # Coverage script
├── Code/
│ ├── Common/ # Tests for Common units
│ ├── ObjectSpace/ # Tests for ObjectSpace units
│ └── Main/ # Test model classes
└── coverage_report/ # Generated coverage reports
Prerequisites¶
DUnitX Environment Variable¶
The $(DUNITX) system environment variable must be set to the DUnitX installation folder:
- Windows: System Properties > Environment Variables
- Delphi IDE: Tools > Options > IDE > Environment Variables
Example: DUNITX=C:\DUnitX
Delphi-Mocks Environment Variable¶
The $(DelphiMocks) system environment variable must be set to the Delphi-Mocks installation folder:
Example: DelphiMocks=C:\Delphi-Mocks\Source
Mocking with Delphi-Mocks¶
Bold uses the Delphi-Mocks framework to create mock objects for unit testing. This is particularly valuable for testing database operations without requiring an actual database connection.
Why Mock Database Operations?¶
Testing Bold persistence code against a real database has several problems:
| Problem | Impact |
|---|---|
| Slow tests | Database connections and queries add significant time |
| Test isolation | Tests can interfere with each other through shared data |
| Setup complexity | Need to create/maintain test databases and data |
| Error simulation | Hard to test error handling (connection failures, deadlocks) |
| CI/CD environments | Build servers may not have database access |
Mocking solves these by replacing database interfaces with controlled test doubles.
Bold Database Interfaces¶
Bold uses interfaces for database abstraction, making them ideal for mocking:
IBoldDatabase- Database connection and transactionsIBoldQuery- SQL query executionIBoldField- Field value access
Example: Mocking a Database Connection¶
uses
Delphi.Mocks,
BoldDBInterfaces;
procedure TMyTest.TestWithMockedDatabase;
var
MockDb: TMock<IBoldDatabase>;
MockQuery: TMock<IBoldQuery>;
begin
// Create mocks
MockDb := TMock<IBoldDatabase>.Create;
MockQuery := TMock<IBoldQuery>.Create;
// Setup: Database returns our mock query
MockDb.Setup
.WillReturn(TValue.From<IBoldQuery>(MockQuery.Instance))
.When.GetQuery;
// Setup: Simulate connected state
MockDb.Setup.WillReturn(True).When.GetConnected;
// Setup: Query returns expected data
MockQuery.Setup.WillReturn(42).When.FieldByName('Id').AsInteger;
// Test code using MockDb.Instance as IBoldDatabase
var Db: IBoldDatabase := MockDb.Instance;
Assert.IsTrue(Db.Connected);
var Query := Db.GetQuery;
Assert.AreEqual(42, Query.FieldByName('Id').AsInteger);
end;
Testing Error Conditions¶
Mocks excel at simulating errors that are hard to reproduce with real databases:
procedure TMyTest.TestConnectionFailure;
var
MockDb: TMock<IBoldDatabase>;
begin
MockDb := TMock<IBoldDatabase>.Create;
// Simulate connection failure
MockDb.Setup.WillReturn(False).When.GetConnected;
MockDb.Setup
.WillRaise(EBoldDatabaseError, 'Connection timeout')
.When.StartTransaction;
// Test that your code handles the error correctly
Assert.WillRaise(
procedure begin
SomeCodeThatNeedsDatabase(MockDb.Instance);
end,
EBoldDatabaseError
);
end;
Verifying Expectations¶
Mocks can verify that expected methods were called:
procedure TMyTest.TestTransactionCommitted;
var
MockDb: TMock<IBoldDatabase>;
begin
MockDb := TMock<IBoldDatabase>.Create;
// Set expectations
MockDb.Setup.Expect.Once.When.StartTransaction;
MockDb.Setup.Expect.Once.When.Commit;
MockDb.Setup.Expect.Never.When.Rollback;
// Run code under test
PerformDatabaseOperation(MockDb.Instance);
// Verify expectations were met
MockDb.Verify; // Fails if expectations not satisfied
end;
Mock Test Location¶
Mock-based tests are in UnitTest/Code/Mocks/. See Test.BoldDBInterfacesMock.pas for complete examples.
Running Tests¶
Command Line¶
Use --exit:Pause to pause before exit:
From Delphi IDE¶
- Open
UnitTest.dprojin Delphi - Build (Shift+F9)
- Run (F9)
TestInsight¶
TestInsight is a Delphi IDE plugin that enables running and debugging individual tests directly from the IDE.
Installing TestInsight¶
- Download from: https://bitbucket.org/sglienke/testinsight/downloads/
- Run the installer
- Restart Delphi IDE
- TestInsight panel appears in View > TestInsight Explorer
Using TestInsight¶
- Open
UnitTest.dprojin Delphi - Open View > TestInsight Explorer
- Build the project (Shift+F9)
- Tests appear in the TestInsight panel
- Run single test: Click the green arrow next to a test
- Debug test: Right-click > Debug Selected Tests
- Run all: Click Run All in the toolbar
TestInsight Features¶
- Run/debug individual tests without running the entire suite
- See test results inline in the IDE
- Double-click failures to jump to the failing line
- Filter tests by name or status
- Re-run failed tests only
TESTINSIGHT Conditional Define¶
| TESTINSIGHT | Behavior |
|---|---|
| Defined | Tests run via TestInsight plugin only |
| Undefined | Tests run as console application |
Toggle via: Right-click project > Enable TestInsight (or Disable)
Writing Tests¶
Test Structure¶
unit Test.MyUnit;
interface
uses
DUnitX.TestFramework;
type
[TestFixture]
TTestMyUnit = class
public
[Setup]
procedure Setup;
[TearDown]
procedure TearDown;
[Test]
procedure TestSomething;
end;
implementation
procedure TTestMyUnit.Setup;
begin
// Initialize before each test
end;
procedure TTestMyUnit.TearDown;
begin
// Cleanup after each test
end;
procedure TTestMyUnit.TestSomething;
begin
Assert.AreEqual(Expected, Actual, 'Description');
end;
initialization
TDUnitX.RegisterTestFixture(TTestMyUnit);
end.
Adding Tests to the Project¶
- Create test file in appropriate
Code/subfolder - Add to
UnitTest.dpruses clause:
Common Assertions¶
Assert.AreEqual(Expected, Actual);
Assert.IsTrue(Condition);
Assert.IsFalse(Condition);
Assert.IsNull(Value);
Assert.IsNotNull(Value);
Assert.WillRaise(procedure begin ... end, EExpectedException);
Code Coverage¶
Code coverage analysis helps identify untested code paths. Bold uses DelphiCodeCoverage.
Running Coverage¶
Options:
| Flag | Description |
|---|---|
-SkipBuild |
Skip compilation, use existing executable |
-OpenReport |
Open HTML report in browser |
-Upload |
Upload to Codecov.io |
Viewing Results¶
Open coverage_report\CodeCoverage_summary.html in a browser.
- Green lines - Executed during tests
- Red lines - Not executed during tests
Tips for Improving Coverage¶
- Focus on uncovered lines - Check the report and target specific uncovered code paths
- Test edge cases - Error handling, boundary conditions, exception paths
- Use persistence tests - Many code paths require database operations
- Test different configurations - Transient vs persistent mode
See Also¶
- Codecov Setup - Upload coverage to Codecov.io
- Contributing - Contribution guidelines
- DUnitX - Unit testing framework
- Delphi-Mocks - Mocking framework