1. Overview
Codion applications follow a layered testing approach, with each layer providing specific testing capabilities:
-
Domain Testing - Validates entity definitions, relationships, and domain logic
-
Model Testing - Tests business logic in edit and table models
-
Integration Testing - Verifies end-to-end functionality with database operations
3. Domain Testing
The DomainTest
base class provides comprehensive testing for entity definitions.
3.1. Basic Domain Test
public class ChinookTest extends DomainTest {
public ChinookTest() {
super(new ChinookImpl(), ChinookEntityFactory::new);
}
@Test
void album() {
test(Album.TYPE);
}
@Test
void artist() {
test(Artist.TYPE);
}
}
The test(EntityType)
method automatically verifies:
-
Entity can be instantiated
-
All attributes are correctly defined
-
Primary keys work correctly
-
Foreign key relationships are valid
-
Insert, update, delete, and select operations succeed
-
Entity validation rules are enforced
3.2. Custom Entity Factory
For entities with complex validation rules or required relationships, provide a custom EntityFactory:
public class ChinookEntityFactory extends DefaultEntityFactory {
@Override
public Entity entity(EntityType entityType) {
if (entityType.equals(InvoiceLine.TYPE)) {
return createInvoiceLine();
}
return super.entity(entityType);
}
private Entity createInvoiceLine() {
Entity invoiceLine = entities.entity(InvoiceLine.TYPE);
// Set required relationships and values
invoiceLine.set(InvoiceLine.INVOICE_FK, randomInvoice());
invoiceLine.set(InvoiceLine.TRACK_FK, randomTrack());
invoiceLine.set(InvoiceLine.QUANTITY, 1);
return invoiceLine;
}
}
3.3. Testing Domain Functions
Test database functions and procedures through the domain:
@Test
void randomPlaylist() {
EntityConnection connection = connection();
connection.startTransaction();
try {
List<Entity> genres = connection.select(limit(Genre.TYPE, 3));
Entity playlist = connection.execute(Playlist.RANDOM_PLAYLIST,
new RandomPlaylistParameters("Test Playlist", 10, genres));
assertNotNull(playlist);
assertEquals(10, connection.count(where(PlaylistTrack.PLAYLIST_FK.equalTo(playlist))));
}
finally {
connection.rollbackTransaction();
}
}
4. Model Testing
Test business logic in your Swing models independently of the UI.
4.1. Edit Model Testing
public class CountryEditModelTest {
@Test
void averageCityPopulation() {
try (EntityConnectionProvider connectionProvider = createConnectionProvider()) {
CountryEditModel countryEditModel = new CountryEditModel(connectionProvider);
// Load a country
countryEditModel.editor().set(connectionProvider.connection()
.selectSingle(Country.NAME.equalTo("Afghanistan")));
// Test computed values
assertEquals(583_025, countryEditModel.averageCityPopulation().get());
// Test with new entity
countryEditModel.editor().defaults();
assertNull(countryEditModel.averageCityPopulation().get());
}
}
}
4.2. Table Model Testing
Test table model behavior and master-detail relationships:
@Test
void albumRefreshedWhenTrackRatingIsUpdated() {
try (EntityConnectionProvider connectionProvider = createConnectionProvider()) {
EntityConnection connection = connectionProvider.connection();
connection.startTransaction();
// Setup test data
Entity album = connection.selectSingle(Album.TITLE.equalTo("Master Of Puppets"));
// Create model and populate
AlbumModel albumModel = new AlbumModel(connectionProvider);
SwingEntityTableModel albumTableModel = albumModel.tableModel();
albumTableModel.queryModel().condition().get(Album.TITLE).set()
.equalTo("Master Of Puppets");
albumTableModel.items().refresh();
// Modify tracks through detail model
List<Entity> tracks = connection.select(Track.ALBUM_FK.equalTo(album));
tracks.forEach(track -> track.set(Track.RATING, 10));
albumModel.detailModels().get(Track.TYPE).editModel().update(tracks);
// Verify album rating was updated
assertEquals(10, albumTableModel.items().visible().get(0).get(Album.RATING));
connection.rollbackTransaction();
}
}
5. Integration Testing
Test complete workflows across multiple entities and models.
5.1. Testing Report Generation
@Test
void countryReport() throws JRException {
EntityConnectionProvider connectionProvider = createConnectionProvider();
EntityConnection connection = connectionProvider.connection();
CountryReportDataSource dataSource =
new CountryReportDataSource(connection, "North America");
Map<String, Object> parameters = new HashMap<>();
parameters.put("CONTINENT", "North America");
JasperPrint jasperPrint = JasperFillManager.fillReport(
loadReport(), parameters, dataSource);
assertNotNull(jasperPrint);
assertFalse(jasperPrint.getPages().isEmpty());
}
6. Test Utilities
6.1. Connection Provider Setup
The LocalEntityConnectionProvider.Builder
requires the codion.db.url
system property to be set:
-Dcodion.db.url=jdbc:h2:mem:h2db
Create connection providers for testing:
private static EntityConnectionProvider createConnectionProvider() {
return LocalEntityConnectionProvider.builder()
.domain(new WorldImpl())
.user(UNIT_TEST_USER)
.build();
}
Alternatively, you can provide your own Database
instance:
private static EntityConnectionProvider createConnectionProvider() {
Database database = H2DatabaseFactory.createDatabase("jdbc:h2:mem:testdb");
return LocalEntityConnectionProvider.builder()
.domain(new WorldImpl())
.database(database)
.user(UNIT_TEST_USER)
.build();
}
6.2. Transaction Management
Always use transactions for data modification tests:
@Test
void updateTest() {
EntityConnection connection = connection();
connection.startTransaction();
try {
// Perform updates
Entity entity = connection.selectSingle(Country.CODE2.equalTo("IS"));
entity.set(Country.POPULATION, 400_000);
connection.update(entity);
// Verify changes
Entity updated = connection.selectSingle(Country.CODE2.equalTo("IS"));
assertEquals(400_000, updated.get(Country.POPULATION));
}
finally {
connection.rollbackTransaction();
}
}
6.3. Test Data Builders
Create fluent builders for complex test data:
public class TestDataBuilder {
public static Entity.Builder customer() {
return entities.entity(Customer.TYPE)
.with(Customer.FIRST_NAME, "Test")
.with(Customer.LAST_NAME, "Customer")
.with(Customer.EMAIL, "test@example.com");
}
public static Entity.Builder invoice(Entity customer) {
return entities.entity(Invoice.TYPE)
.with(Invoice.CUSTOMER_FK, customer)
.with(Invoice.INVOICE_DATE, LocalDate.now())
.with(Invoice.TOTAL, BigDecimal.ZERO);
}
}
7. Testing Best Practices
-
Use DomainTest for all entity types - Even simple entities benefit from the comprehensive validation
-
Test with transactions - Always rollback to keep tests isolated
-
Test observable behavior - Verify that model state changes trigger appropriate notifications
-
Test validation rules - Ensure domain constraints are properly enforced
-
Test computed values - Verify derived attributes and denormalized values update correctly
-
Keep tests focused - Each test should verify one specific behavior
-
Use realistic test data - Your entity factories should create valid, meaningful entities