Loading...
Loading...
Provides patterns for unit testing mappers and converters (MapStruct, custom mappers). Validates object transformation logic in isolation. Use when ensuring correct data transformation between DTOs and domain objects.
npx skill4agent add giuseppe-trisciuoglio/developer-kit-claude-code unit-test-mapper-converter<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>dependencies {
implementation("org.mapstruct:mapstruct:1.5.5.Final")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.assertj:assertj-core")
}// Mapper interface
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDto toDto(User user);
User toEntity(UserDto dto);
List<UserDto> toDtos(List<User> users);
}
// Unit test
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class UserMapperTest {
private final UserMapper userMapper = Mappers.getMapper(UserMapper.class);
@Test
void shouldMapUserToDto() {
User user = new User(1L, "Alice", "alice@example.com", 25);
UserDto dto = userMapper.toDto(user);
assertThat(dto)
.isNotNull()
.extracting("id", "name", "email", "age")
.containsExactly(1L, "Alice", "alice@example.com", 25);
}
@Test
void shouldMapDtoToEntity() {
UserDto dto = new UserDto(1L, "Alice", "alice@example.com", 25);
User user = userMapper.toEntity(dto);
assertThat(user)
.isNotNull()
.hasFieldOrPropertyWithValue("id", 1L)
.hasFieldOrPropertyWithValue("name", "Alice");
}
@Test
void shouldMapListOfUsers() {
List<User> users = List.of(
new User(1L, "Alice", "alice@example.com", 25),
new User(2L, "Bob", "bob@example.com", 30)
);
List<UserDto> dtos = userMapper.toDtos(users);
assertThat(dtos)
.hasSize(2)
.extracting(UserDto::getName)
.containsExactly("Alice", "Bob");
}
@Test
void shouldHandleNullEntity() {
UserDto dto = userMapper.toDto(null);
assertThat(dto).isNull();
}
}// Entities with nesting
class User {
private Long id;
private String name;
private Address address;
private List<Phone> phones;
}
// Mapper with nested mapping
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDto toDto(User user);
User toEntity(UserDto dto);
}
// Unit test for nested objects
class NestedObjectMapperTest {
private final UserMapper userMapper = Mappers.getMapper(UserMapper.class);
@Test
void shouldMapNestedAddress() {
Address address = new Address("123 Main St", "New York", "NY", "10001");
User user = new User(1L, "Alice", address);
UserDto dto = userMapper.toDto(user);
assertThat(dto.getAddress())
.isNotNull()
.hasFieldOrPropertyWithValue("street", "123 Main St")
.hasFieldOrPropertyWithValue("city", "New York");
}
@Test
void shouldMapListOfNestedPhones() {
List<Phone> phones = List.of(
new Phone("123-456-7890", "MOBILE"),
new Phone("987-654-3210", "HOME")
);
User user = new User(1L, "Alice", null, phones);
UserDto dto = userMapper.toDto(user);
assertThat(dto.getPhones())
.hasSize(2)
.extracting(PhoneDto::getNumber)
.containsExactly("123-456-7890", "987-654-3210");
}
@Test
void shouldHandleNullNestedObjects() {
User user = new User(1L, "Alice", null);
UserDto dto = userMapper.toDto(user);
assertThat(dto.getAddress()).isNull();
}
}@Mapper(componentModel = "spring")
public interface ProductMapper {
@Mapping(source = "name", target = "productName")
@Mapping(source = "price", target = "salePrice")
@Mapping(target = "discount", expression = "java(product.getPrice() * 0.1)")
ProductDto toDto(Product product);
@Mapping(source = "productName", target = "name")
@Mapping(source = "salePrice", target = "price")
Product toEntity(ProductDto dto);
}
class CustomMappingTest {
private final ProductMapper mapper = Mappers.getMapper(ProductMapper.class);
@Test
void shouldMapFieldsWithCustomNames() {
Product product = new Product(1L, "Laptop", 999.99);
ProductDto dto = mapper.toDto(product);
assertThat(dto)
.hasFieldOrPropertyWithValue("productName", "Laptop")
.hasFieldOrPropertyWithValue("salePrice", 999.99);
}
@Test
void shouldCalculateDiscountFromExpression() {
Product product = new Product(1L, "Laptop", 100.0);
ProductDto dto = mapper.toDto(product);
assertThat(dto.getDiscount()).isEqualTo(10.0);
}
@Test
void shouldReverseMapCustomFields() {
ProductDto dto = new ProductDto(1L, "Laptop", 999.99);
Product product = mapper.toEntity(dto);
assertThat(product)
.hasFieldOrPropertyWithValue("name", "Laptop")
.hasFieldOrPropertyWithValue("price", 999.99);
}
}// Enum with different representation
enum UserStatus { ACTIVE, INACTIVE, SUSPENDED }
enum UserStatusDto { ENABLED, DISABLED, LOCKED }
@Mapper(componentModel = "spring")
public interface UserMapper {
@ValueMapping(source = "ACTIVE", target = "ENABLED")
@ValueMapping(source = "INACTIVE", target = "DISABLED")
@ValueMapping(source = "SUSPENDED", target = "LOCKED")
UserStatusDto toStatusDto(UserStatus status);
}
class EnumMapperTest {
private final UserMapper mapper = Mappers.getMapper(UserMapper.class);
@Test
void shouldMapActiveToEnabled() {
UserStatusDto dto = mapper.toStatusDto(UserStatus.ACTIVE);
assertThat(dto).isEqualTo(UserStatusDto.ENABLED);
}
@Test
void shouldMapSuspendedToLocked() {
UserStatusDto dto = mapper.toStatusDto(UserStatus.SUSPENDED);
assertThat(dto).isEqualTo(UserStatusDto.LOCKED);
}
}// Custom converter class
public class DateFormatter {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static String format(LocalDate date) {
return date != null ? date.format(formatter) : null;
}
public static LocalDate parse(String dateString) {
return dateString != null ? LocalDate.parse(dateString, formatter) : null;
}
}
// Unit test
class DateFormatterTest {
@Test
void shouldFormatLocalDateToString() {
LocalDate date = LocalDate.of(2024, 1, 15);
String result = DateFormatter.format(date);
assertThat(result).isEqualTo("2024-01-15");
}
@Test
void shouldParseStringToLocalDate() {
String dateString = "2024-01-15";
LocalDate result = DateFormatter.parse(dateString);
assertThat(result).isEqualTo(LocalDate.of(2024, 1, 15));
}
@Test
void shouldHandleNullInFormat() {
String result = DateFormatter.format(null);
assertThat(result).isNull();
}
@Test
void shouldHandleInvalidDateFormat() {
assertThatThrownBy(() -> DateFormatter.parse("invalid-date"))
.isInstanceOf(DateTimeParseException.class);
}
}class BidirectionalMapperTest {
private final UserMapper mapper = Mappers.getMapper(UserMapper.class);
@Test
void shouldMaintainDataInRoundTrip() {
User original = new User(1L, "Alice", "alice@example.com", 25);
UserDto dto = mapper.toDto(original);
User restored = mapper.toEntity(dto);
assertThat(restored)
.hasFieldOrPropertyWithValue("id", original.getId())
.hasFieldOrPropertyWithValue("name", original.getName())
.hasFieldOrPropertyWithValue("email", original.getEmail())
.hasFieldOrPropertyWithValue("age", original.getAge());
}
@Test
void shouldPreserveAllFieldsInBothDirections() {
Address address = new Address("123 Main", "NYC", "NY", "10001");
User user = new User(1L, "Alice", "alice@example.com", 25, address);
UserDto dto = mapper.toDto(user);
User restored = mapper.toEntity(dto);
assertThat(restored).usingRecursiveComparison().isEqualTo(user);
}
}@Mapper(componentModel = "spring")
public interface UserMapper {
void updateEntity(@MappingTarget User entity, UserDto dto);
}
class PartialMapperTest {
private final UserMapper mapper = Mappers.getMapper(UserMapper.class);
@Test
void shouldUpdateExistingEntity() {
User existing = new User(1L, "Alice", "alice@old.com", 25);
UserDto dto = new UserDto(1L, "Alice", "alice@new.com", 26);
mapper.updateEntity(existing, dto);
assertThat(existing)
.hasFieldOrPropertyWithValue("email", "alice@new.com")
.hasFieldOrPropertyWithValue("age", 26);
}
@Test
void shouldNotUpdateFieldsNotInDto() {
User existing = new User(1L, "Alice", "alice@example.com", 25);
UserDto dto = new UserDto(1L, "Bob", null, 0);
mapper.updateEntity(existing, dto);
// Assuming null-aware mapping is configured
assertThat(existing.getEmail()).isEqualTo("alice@example.com");
}
}nullValuePropertyMappingStrategynullValueCheckStrategy@Mapper@ValueMapping