Files
spring-boot-demo/mapstruct/src/test/java/com/ankurm/mapstruct/TestingStrategiesTests.java
T

125 lines
6.3 KiB
Java

package com.ankurm.mapstruct;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.ankurm.mapstruct.domain.Customer;
import com.ankurm.mapstruct.dto.AddressDto;
import com.ankurm.mapstruct.dto.CustomerDto;
import com.ankurm.mapstruct.mapper.AddressMapper;
import com.ankurm.mapstruct.mapper.AddressMapperImpl;
import com.ankurm.mapstruct.mapper.CustomerMapper;
import com.ankurm.mapstruct.mapper.CustomerMapperImpl;
import com.ankurm.mapstruct.mapper.OrderMapperImpl;
import java.time.Clock;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
/** Four ways to test a generated mapper, and one way to make a new DTO component fail the build until it is tested. */
class TestingStrategiesTests {
// ---- 1. plain unit tests ---------------------------------------------------------------------------
@Test
void plainUnitTests() {
try (var t = new Transcript("11-testing-mappers-plain.txt", "Testing a generated mapper without Spring: Mappers.getMapper versus constructing the Impl")) {
AddressMapper viaFactory = Mappers.getMapper(AddressMapper.class);
t.section("1. Mappers.getMapper(...)");
t.line("AddressMapper (no dependencies) : %s", viaFactory.getClass().getSimpleName());
Throwable thrown = null;
try {
Mappers.getMapper(CustomerMapper.class);
} catch (RuntimeException e) {
thrown = e;
}
t.line("CustomerMapper (uses = {...}) : %s", thrown == null ? "worked" : thrown.getClass().getSimpleName());
Throwable cause = thrown;
while (cause != null && cause.getCause() != null) {
cause = cause.getCause();
}
t.line(" root cause : %s", cause == null ? "-" : cause.getClass().getName() + ": " + cause.getMessage());
CustomerMapper byHand = new CustomerMapperImpl(new AddressMapperImpl(), new OrderMapperImpl());
t.section("2. new CustomerMapperImpl(new AddressMapperImpl(), new OrderMapperImpl())");
t.line("no Spring, no reflection : %s", byHand.toDto(Fixtures.customer(), Fixtures.clock()).fullName());
assertThat(viaFactory).isInstanceOf(AddressMapperImpl.class);
assertThat(thrown).isNotNull();
assertThat(cause).isInstanceOf(NoSuchMethodException.class);
assertThat(byHand.toDto(Fixtures.customer(), Fixtures.clock()).fullName()).isEqualTo("Asha Mehta");
}
}
// ---- a property-level strategy ------------------------------------------------------------------
/** One mutation per DTO component: change the entity, and exactly that component must change. */
private static Map<String, Mutation> mutations() {
Map<String, Mutation> m = new LinkedHashMap<>();
m.put("id", new Mutation(c -> c.setId(42L), CustomerDto::id));
m.put("fullName", new Mutation(c -> c.setLastName("Shah"), CustomerDto::fullName));
m.put("email", new Mutation(c -> c.setEmail("[email protected]"), CustomerDto::email));
m.put("birthDate", new Mutation(c -> c.setBirthDate(LocalDate.of(2000, 1, 1)), CustomerDto::birthDate));
m.put("age", new Mutation(c -> c.setBirthDate(LocalDate.of(2000, 1, 1)), CustomerDto::age));
m.put("nickname", new Mutation(c -> c.setNickname("ash"), CustomerDto::nickname));
m.put("address", new Mutation(c -> c.getAddress().setCity("Nagpur"), CustomerDto::address));
m.put("roles", new Mutation(c -> c.getRoles().add("ADMIN"), CustomerDto::roles));
m.put("attributes", new Mutation(c -> c.getAttributes().put("k", "v"), CustomerDto::attributes));
m.put("orders", new Mutation(c -> c.getOrders().get(0).getLines().get(0).setQuantity(9), CustomerDto::orders));
return m;
}
private record Mutation(Consumer<Customer> change, Function<CustomerDto, Object> read) { }
@Test
void everyDtoComponentIsCoveredByAMutationTest() {
try (var t = new Transcript("14-property-level-test-strategy.txt",
"Property-level mapper test: change one entity property, expect exactly the right DTO component to change")) {
var mapper = Fixtures.customerMapper();
Clock clock = Fixtures.clock();
CustomerDto base = mapper.toDto(Fixtures.customer(), clock);
List<String> failures = new ArrayList<>();
mutations().forEach((component, mutation) -> {
Customer changed = Fixtures.customer();
mutation.change().accept(changed);
CustomerDto after = mapper.toDto(changed, clock);
boolean moved = !mutation.read().apply(after).equals(mutation.read().apply(base));
t.line("%-11s changes when its source changes : %s", component, moved);
if (!moved) {
failures.add(component);
}
});
var components = new TreeSet<String>();
for (var rc : CustomerDto.class.getRecordComponents()) {
components.add(rc.getName());
}
components.remove("origin"); // a constant: there is no source property to change
t.blank();
t.line("record components : %s", components);
t.line("covered by a test : %s", new TreeSet<>(mutations().keySet()));
assertThat(failures).isEmpty();
assertThat(new TreeSet<>(mutations().keySet())).isEqualTo(components);
assertThat(base.origin()).isEqualTo("api");
}
}
@Test
void mapperIsUsableThroughItsInterfaceOnly() {
// The same assertion style a team would copy: build the expected record by hand.
CustomerDto dto = Fixtures.customerMapper().toDto(Fixtures.customer(), Fixtures.clock());
assertThat(dto.address()).isEqualTo(new AddressDto("12 Park Road", "Pune", "411001", "IN"));
assertThatThrownBy(() -> Mappers.getMapper(CustomerMapper.class)).isInstanceOf(RuntimeException.class);
}
}