Did I Map Everything?
When migrating database schemas, you rarely get clean, predictable data. Legacy databases often have cryptic column names, partially backfilled fields, nullable values, or columns that are completely empty. That’s why you write custom Java JDBC mappers in the first place: to explicitly control how legacy database columns map to your target Java objects, rather than relying on automatic mapping that may not handle these inconsistencies correctly. But there’s a catch: standard unit tests can pass even when you completely forget to map a field. Because null == null, a missing mapping line looks identical to a column that is legitimately empty in your database.
Here’s a simple reflection trick to verify that your custom mapper covers 100% of your target Java class, so missing mappings don’t slip through unnoticed.
The Trap
Imagine your clean target Java class looks like this:
package com.example;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class User {
private Long id;
private String fullName;
private String emailAddress;
private String migrationRefCode; // Can be NULL in DB during migration
}
Your custom mapper looks like this (notice that the MIG_REF_CD mapping is commented out):
package com.example;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
long id = rs.getLong("USR_ID");
user.setId(rs.wasNull() ? null : id);
user.setFullName(rs.getString("FULL_NM"));
user.setEmailAddress(rs.getString("EMAIL_ADDR"));
// Forgot: user.setMigrationRefCode(rs.getString("MIG_REF_CD"));
return user;
}
}
If you write a standard unit test where MIG_REF_CD is stubbed as null (or left unstubbed), your test passes with flying colors. The test expects migrationRefCode to be null, it is null, and the missing line of code goes totally undetected until production.
The Solution: Fake 100% Data in Your Unit Test
Don’t use a real database connection for this test. Instead, mock the ResultSet so it guarantees non-null fake data for every single getter call, regardless of whether the real database column is nullable or not.
The Reflection Helper
To verify that no fields were skipped, create a simple test utility using standard Java Reflection to inspect every declared field on the object:
package com.example.util;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class MapperTestUtils {
public static void assertAllFieldsMapped(Object target) {
assertNotNull(target, "Target mapped object cannot be null!");
Class<?> clazz = target.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
Object value = field.get(target);
assertNotNull(
value,
"You forgot to map field: '" + field.getName() + "' in " + clazz.getSimpleName() + "!"
);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to access field: " + field.getName(), e);
}
}
}
}
The Complete Test Code
Now, stub your ResultSet to return fake values for every field, map the row, and invoke assertAllFieldsMapped(user):
package com.example;
import org.junit.jupiter.api.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
import static com.example.util.MapperTestUtils.assertAllFieldsMapped;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class UserMapperTest {
@Test
void verifyMappedValuesMatchFakeData() throws SQLException {
ResultSet rs = mock(ResultSet.class);
when(rs.getString(anyString())).thenReturn("FAKE_DATA");
when(rs.getLong(anyString())).thenReturn(100L);
UserMapper mapper = new UserMapper();
User user = mapper.mapRow(rs, 1);
assertEquals(100L, user.getId());
assertEquals("FAKE_DATA", user.getFullName());
assertEquals("FAKE_DATA", user.getEmailAddress());
}
@Test
void ensureEveryJavaFieldIsMapped() throws SQLException {
ResultSet rs = mock(ResultSet.class);
when(rs.getString(anyString())).thenReturn("FAKE_DATA");
when(rs.getLong(anyString())).thenReturn(100L);
UserMapper mapper = new UserMapper();
User user = mapper.mapRow(rs, 1);
assertEquals(100L, user.getId());
assertEquals("FAKE_DATA", user.getFullName());
assertEquals("FAKE_DATA", user.getEmailAddress());
assertAllFieldsMapped(user);
}
}
Test Execution Results
When you run both tests against the incomplete UserMapper:
-
verifyMappedValuesMatchFakeData()PASSED: This traditional test only asserts the fields you explicitly check. Because it doesn’t inspectmigrationRefCode, it green-lights a broken mapper. -
ensureEveryJavaFieldIsMapped()FAILED: This test invokesassertAllFieldsMapped(user)and fails instantly with an actionable assertion error:
Conclusion
Silent mapping omissions are one of the most frustrating sources of post-migration bugs because standard unit tests let them slip right through. By pairing non-null Mockito stubs with a reflection-based field check, you establish an automated fail-fast safety net across your entire data access layer. Incorporating this quick utility into your test pipeline guarantees 100% field mapping coverage, helps catch missing mappings early.