
Unit Test Bean Validation
Write JUnit tests for Jakarta Bean Validation on DTOs, including validation groups and parameterized invalid-input cases.
Overview
unit-test-bean-validation is an agent skill most often used in Ship (also Build backend) that unit-tests Jakarta Bean Validation with groups and parameterized invalid-input cases.
Install
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill unit-test-bean-validationWhat is this skill?
- Validation groups pattern: CreateValidation, UpdateValidation, AdminValidation on DTO constraints
- Tests selective rules per group with validator.validate(dto, Group.class)
- Parameterized @ValueSource and similar cases for email and boundary invalid input
- Extends BaseValidationTest-style harness for reusable Validator setup
- Documents multi-group validate calls when creation and update rules overlap
Adoption & trust: 2.3k installs on skills.sh; 271 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your API DTOs use Bean Validation annotations but you lack focused unit tests for group-specific rules and edge-case inputs.
Who is it for?
Indie Java/Kotlin backend devs adding or refactoring DTO validation tests in Spring or Jakarta EE style projects.
Skip if: Non-JVM stacks, teams that only do end-to-end HTTP tests for validation, or greenfield apps with zero Bean Validation on models.
When should I use this skill?
User needs JUnit unit tests for Bean Validation, validation groups, or ConstraintViolation assertions on Java DTOs.
What do I get? / Deliverables
You get test classes that assert violations per property path and per validation group, ready to run in plain JUnit without a web layer.
- JUnit test classes for validation groups and parameterized constraint cases
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Bean validation unit tests are canonical Ship work—proving request DTO rules before release—while patterns also help when hardening APIs during Build. Centers on ConstraintViolation assertions, groups (Create vs Update), and parameterized ValueSource tests in the testing subphase.
Where it fits
Add CreateValidation tests when introducing a new signup DTO before wiring the controller.
Expand parameterized invalid-email cases before tagging a REST API release.
Add UpdateValidation coverage after loosening optional fields on a PATCH endpoint.
How it compares
Focused Validator unit-test recipes—not a full Testcontainers or REST Assured API suite.
Common Questions / FAQ
Who is unit-test-bean-validation for?
Solo and small-team JVM developers who want reliable, fast tests around Jakarta Bean Validation on request and domain DTOs.
When should I use unit-test-bean-validation?
Use in Ship while adding regression tests before release, and in Build when introducing new DTOs or split Create/Update validation groups; also when hardening forms after a production validation bug.
Is unit-test-bean-validation safe to install?
It is test-pattern documentation only; review the Security Audits panel on this page and do not embed real PII in parameterized test fixtures.
SKILL.md
READMESKILL.md - Unit Test Bean Validation
# Advanced Validation Patterns Reference ## Validation Groups ### Defining Groups ```java public interface CreateValidation {} public interface UpdateValidation {} public interface AdminValidation {} ``` ### Using Groups in DTOs ```java class UserDto { @NotNull(groups = CreateValidation.class) private String name; @Min(value = 0, groups = {CreateValidation.class, UpdateValidation.class}) private int age; } ``` ### Testing Groups ```java class ValidationGroupsTest extends BaseValidationTest { @Test void shouldRequireNameOnlyDuringCreation() { UserDto user = new UserDto(null, 25); Set<ConstraintViolation<UserDto>> violations = validator.validate(user, CreateValidation.class); assertThat(violations) .extracting(ConstraintViolation::getPropertyPath) .extracting(Path::toString) .contains("name"); } @Test void shouldAllowNullNameDuringUpdate() { UserDto user = new UserDto(null, 25); assertThat(validator.validate(user, UpdateValidation.class)).isEmpty(); } @Test void shouldValidateMultipleGroups() { UserDto user = new UserDto("Alice", -5); Set<ConstraintViolation<UserDto>> violations = validator.validate(user, CreateValidation.class, UpdateValidation.class); assertThat(violations).isNotEmpty(); } } ``` ## Parameterized Tests ### Email Validation ```java class EmailValidationTest extends BaseValidationTest { @ParameterizedTest @ValueSource(strings = { "user@example.com", "john.doe+tag@example.co.uk", "admin@subdomain.example.com" }) void shouldAcceptValidEmails(String email) { UserDto user = new UserDto("Alice", email); assertThat(validator.validate(user)).isEmpty(); } @ParameterizedTest @ValueSource(strings = { "invalid-email", "user@", "@example.com", "user name@example.com" }) void shouldRejectInvalidEmails(String email) { UserDto user = new UserDto("Alice", email); assertThat(validator.validate(user)).isNotEmpty(); } } ``` ### Multiple Parameters ```java class RangeValidationTest extends BaseValidationTest { @ParameterizedTest @CsvSource({ "0, 100, true", "-1, 100, false", "0, 0, false", "50, 100, true" }) void shouldValidateRange(int min, int max, boolean shouldPass) { RangeDto dto = new RangeDto(min, max); var violations = validator.validate(dto); assertThat(violations.isEmpty()).isEqualTo(shouldPass); } } ``` ## Debugging Failed Tests ### When Tests Fail 1. **Check violation count**: `assertThat(violations).hasSize(n)` 2. **Inspect property path**: `violation.getPropertyPath().toString()` 3. **Verify message**: `violation.getMessage()` 4. **Check invalid value**: `violation.getInvalidValue()` ```java @Test void debugFailedValidation() { UserDto user = new UserDto("", "invalid"); Set<ConstraintViolation<UserDto>> violations = validator.validate(user); // Debug output violations.forEach(v -> System.out.println( v.getPropertyPath() + ": " + v.getMessage() )); assertThat(violations).hasSize(2); } ``` ### Common Issues | Issue | Cause | Solution | |-------|-------|----------| | `ConstraintViolation` null | Object is valid or constraint doesn't fire | Check annotation parameters | | Wrong property path | Wrong field annotated | Verify `@Constraint(validatedBy=)` | | Null passes validation | Constraint allows null | Add `@NotNull` | | Multiple violations | Multiple constraints fail | Use `hasSize()` to verify count | # Custom Validators Testing Reference ## Creating Custom Constraints ### Annotation Definition ```java @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PhoneNumberValidator.class) public @interface ValidPhoneNumber { String message() default "invalid phone number format"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` ### Validator Implementation ```java public class PhoneNumberValidator implements Constrai