Should I test that?

Should I test DTOs?

Verdict

No

Do not write unit tests for a DTO that only declares fields and generated accessors; check its JSON in the test of the endpoint that sends or receives it.

Why

Do not write unit tests for a DTO that only declares fields and accessors. For the typical case, a request or response class written as a Java record or a Lombok class, Blast radius is users and Change frequency is rarely, because a wrong field reaches a client screen and a given DTO gains a field a few times a year. Detectability is immediately: the compiler rejects a removed field in the code that fills it, and the endpoint test that serializes the DTO fails in CI. Reversibility and Test cost are both trivial, because a revert leaves no bad data behind and a round-trip test takes minutes. Rule R8 gives Do not test, and validation or mapping code moves the decision to a test.

When the decision changes
WhenDecisionWhy
The DTO carries validation annotations, such as `@NotNull`, on the body of a write endpointTest: one valid object and one invalid object per ruleDetectability moves to eventually and Reversibility to with-effort: a missing rule stores bad records without an error
Installed mobile apps or a partner service that you do not ship read the DTO's JSONTest minimally: one serialization test per DTO that asserts the field namesDetectability moves to eventually, Reversibility to with-effort and Test cost to moderate: only clients on old versions see a renamed field
The DTO carries an amount that a payment provider chargesTest mandatory: assert the exact JSON for amounts such as 0.10 and 19.99Blast radius rises to money, Detectability to eventually and Reversibility to costly: a wrong charge is refunded by hand
The response DTO is the only place that keeps a password hash or a home address out of the responseTest mandatory: assert that each excluded field is absent from the JSONBlast radius rises to safety-or-legal, Detectability to never and Reversibility to impossible: leaked data stays leaked
The DTO has a hand-written method that copies fields from an entity, such as `fromEntity()`Test: fill every source field and check every DTO fieldDetectability moves to eventually: a forgotten field arrives as null and looks like missing data

What breaks if you don't test

For a DTO without logic, nothing breaks that the compiler and the endpoint tests miss. The failures that reach users sit at the edges of the DTO: a JSON name that the serializer derives from an accessor, a validation annotation that never runs, a mapper that forgets a new field. A mobile app then shows an empty value, or a write endpoint saves an order without a delivery address, and support tickets report it days later.

What you lose if you over-test

A test per field adds a test file for every DTO. On an API with 60 DTOs, 60 files repeat each field list, and every new field needs an edit in two places. The tests never fail, because a generated accessor cannot drift from its field. They lift coverage while the JSON names stay unchecked, so a coverage gate reports the DTO package as done.

What to do instead

In the HTTP-level test of each endpoint, assert the JSON fields that the client reads, so a renamed or missing field fails CI. Generate accessors, equals and hashCode with records or Lombok so there is no hand-written body to get wrong. For a DTO that another service reads, add one test with Spring Boot's @JsonTest, as the Spring Boot guide to JSON tests shows.

When the answer changes

  • The DTO gains a method with logic, such as validation or a copy from an entity.
  • A client that you do not ship, such as an installed mobile app, reads the JSON field names.
  • The DTO carries money amounts or personal data.

Code example + Counterexample

The boolean field that lost its prefix

This Lombok DTO has no hand-written code, and it still breaks the API:

@Data
@AllArgsConstructor
public class AccountDto {
    private String email;
    private boolean isPremium;
}

@JsonTest
class AccountDtoJsonTest {
    @Autowired JacksonTester<AccountDto> json;

    @Test
    void writesTheFieldNamesTheAppReads() throws Exception {
        AccountDto dto = new AccountDto("ana@example.com", true);
        assertThat(json.write(dto))
            .hasJsonPathBooleanValue("@.isPremium"); // fails: Jackson writes "premium"
    }
}

Lombok names the getter isPremium() for a boolean field that starts with is, and Jackson derives the property name premium from that getter. A client that reads isPremium gets undefined and hides paid features from a paying customer, while the compiler and a round-trip test of the getter see nothing wrong. Detectability moves to eventually and Test cost to moderate, so the decision moves from Do not test to Test minimally. The fix is to rename the field to premium and set its JSON name with @JsonProperty("isPremium").

FAQ

Is it worth writing a unit test for a DTO with basic getters and setters?

No, a DTO with only basic getters and setters needs no unit test, because Lombok or the compiler generates accessors without logic. Check the DTO's JSON in the endpoint test instead, where a renamed field fails CI.

Should POJOs be tested?

No, a POJO that only holds fields needs no tests of its own; test the code that creates and reads it. A POJO method with logic, such as a hand-written equals, gets tests like any other method.

Should I test DTO validation annotations?

Yes, test DTO validation annotations with one valid and one invalid object per rule, using the Bean Validation Validator in a plain unit test. Add one endpoint test that expects a 400 response for an invalid body, because Spring runs the annotations only when the controller parameter carries @Valid.

Should I test the mapping between entities and DTOs?

Yes, test a hand-written entity-to-DTO mapper with one test that fills every source field and checks every DTO field, because a forgotten field arrives as null. With MapStruct, set unmappedTargetPolicy = ReportingPolicy.ERROR so the build fails on a DTO field that no source fills.