refactor HsOfficePartnerEntityPatchUnitTest to PatchUnitTestBase

This commit is contained in:
Michael Hoennig 2022-09-24 12:43:42 +02:00
parent d08e60f8dc
commit 3e6da45302
4 changed files with 312 additions and 272 deletions

View File

@ -0,0 +1,6 @@
package net.hostsharing.hsadminng.hs.office.partner;
public interface EntityPatch<R> {
void apply(R resource);
}

View File

@ -9,8 +9,9 @@ import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
class HsOfficePartnerEntityPatch { class HsOfficePartnerEntityPatch implements EntityPatch<HsOfficePartnerPatchResource> {
private final HsOfficePartnerEntity entity; private final HsOfficePartnerEntity entity;
private final Function<UUID, Optional<HsOfficeContactEntity>> fetchContact; private final Function<UUID, Optional<HsOfficeContactEntity>> fetchContact;
@ -25,16 +26,17 @@ class HsOfficePartnerEntityPatch {
this.fetchPerson = fetchPerson; this.fetchPerson = fetchPerson;
} }
void apply(final HsOfficePartnerPatchResource resource) { @Override
public void apply(final HsOfficePartnerPatchResource resource) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> { OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> {
entity.setContact(fetchContact.apply(newValue).orElseThrow( verifyNotNull(newValue, "contact");
() -> new NoSuchElementException("cannot find contact uuid " + newValue) entity.setContact(fetchContact.apply(newValue)
)); .orElseThrow(noSuchElementException("contact", newValue)));
}); });
OptionalFromJson.of(resource.getPersonUuid()).ifPresent(newValue -> { OptionalFromJson.of(resource.getPersonUuid()).ifPresent(newValue -> {
entity.setPerson(fetchPerson.apply(newValue).orElseThrow( verifyNotNull(newValue, "person");
() -> new NoSuchElementException("cannot find person uuid " + newValue) entity.setPerson(fetchPerson.apply(newValue)
)); .orElseThrow(noSuchElementException("person", newValue)));
}); });
OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice); OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice);
OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber); OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber);
@ -42,4 +44,14 @@ class HsOfficePartnerEntityPatch {
OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName); OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName);
OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath); OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath);
} }
private Supplier<RuntimeException> noSuchElementException(final String propertyName, final UUID newValue) {
return () -> new NoSuchElementException("cannot find '" + propertyName + "' uuid " + newValue);
}
private void verifyNotNull(final UUID newValue, final String propertyName) {
if (newValue == null) {
throw new IllegalArgumentException("property '" + propertyName + "' must not be null");
}
}
} }

View File

@ -3,23 +3,20 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openapitools.jackson.nullable.JsonNullable;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
// TODO: there must be an easier way to test such patch classes @TestInstance(PER_CLASS)
class HsOfficePartnerEntityPatchUnitTest { class HsOfficePartnerEntityPatchUnitTest extends PatchUnitTestBase<
HsOfficePartnerPatchResource,
HsOfficePartnerEntity
> {
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID(); private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID();
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID(); private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
@ -31,198 +28,84 @@ class HsOfficePartnerEntityPatchUnitTest {
private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31"); private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31");
private static final LocalDate INITIAL_DAY_OF_DEATH = LocalDate.parse("2000-01-01"); private static final LocalDate INITIAL_DAY_OF_DEATH = LocalDate.parse("2000-01-01");
private static final LocalDate PATCHED_DAY_OF_DEATH = LocalDate.parse("2022-08-31"); private static final LocalDate PATCHED_DATE_OF_DEATH = LocalDate.parse("2022-08-31");
final HsOfficePartnerEntity givenPartner = new HsOfficePartnerEntity(); private final HsOfficePersonEntity givenInitialPerson = HsOfficePersonEntity.builder()
private final HsOfficePersonEntity givenInitialPerson = new HsOfficePersonEntity(); .uuid(INITIAL_PERSON_UUID)
private final HsOfficeContactEntity givenInitialContact = new HsOfficeContactEntity(); .build();
private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder()
.uuid(INITIAL_CONTACT_UUID)
.build();
final HsOfficePartnerPatchResource patchResource = new HsOfficePartnerPatchResource(); @Override
HsOfficePartnerEntity newInitialEntity() {
final var p = new HsOfficePartnerEntity();
p.setUuid(INITIAL_PARTNER_UUID);
p.setPerson(givenInitialPerson);
p.setContact(givenInitialContact);
p.setRegistrationOffice("initial Reg-Office");
p.setRegistrationNumber("initial Reg-Number");
p.setBirthday(INITIAL_BIRTHDAY);
p.setBirthName("initial birth name");
p.setDateOfDeath(INITIAL_DAY_OF_DEATH);
return p;
}
private final HsOfficePartnerEntityPatch hsOfficePartnerEntityPatch = new HsOfficePartnerEntityPatch( @Override
givenPartner, HsOfficePartnerPatchResource newPatchResource() {
return new HsOfficePartnerPatchResource();
}
@Override
HsOfficePartnerEntityPatch createPatcher(final HsOfficePartnerEntity partner) {
return new HsOfficePartnerEntityPatch(
partner,
uuid -> uuid == PATCHED_CONTACT_UUID uuid -> uuid == PATCHED_CONTACT_UUID
? Optional.of(newContact(uuid)) ? Optional.of(newContact(uuid))
: Optional.empty(), : Optional.empty(),
uuid -> uuid == PATCHED_PERSON_UUID uuid -> uuid == PATCHED_PERSON_UUID
? Optional.of(newPerson(uuid)) ? Optional.of(newPerson(uuid))
: Optional.empty()); : Optional.empty());
{
givenInitialPerson.setUuid(INITIAL_PERSON_UUID);
givenInitialContact.setUuid(INITIAL_CONTACT_UUID);
givenPartner.setUuid(INITIAL_PARTNER_UUID);
givenPartner.setPerson(givenInitialPerson);
givenPartner.setContact(givenInitialContact);
givenPartner.setRegistrationOffice("initial Reg-Office");
givenPartner.setRegistrationNumber("initial Reg-Number");
givenPartner.setBirthday(INITIAL_BIRTHDAY);
givenPartner.setBirthName("initial birth name");
givenPartner.setDateOfDeath(INITIAL_DAY_OF_DEATH);
} }
@Test @Override
void willPatchAllProperties() { Stream<TestCase> testCases() {
// given return Stream.of(
patchResource.setContactUuid(JsonNullable.of(PATCHED_CONTACT_UUID)); new TestCase(
patchResource.setPersonUuid(JsonNullable.of(PATCHED_PERSON_UUID)); "contact",
patchResource.setRegistrationNumber(JsonNullable.of("patched Reg-Number")); HsOfficePartnerPatchResource::setContactUuid,
patchResource.setRegistrationOffice(JsonNullable.of("patched Reg-Office")); PATCHED_CONTACT_UUID,
patchResource.setBirthday(JsonNullable.of(PATCHED_BIRTHDAY)); HsOfficePartnerEntity::setContact,
patchResource.setBirthName(JsonNullable.of("patched birth name")); newContact(PATCHED_CONTACT_UUID))
patchResource.setDateOfDeath(JsonNullable.of(PATCHED_DAY_OF_DEATH)); .notNullable()
.resolvesUuid(),
// when new TestCase(
hsOfficePartnerEntityPatch.apply(patchResource); "person",
HsOfficePartnerPatchResource::setPersonUuid,
// then PATCHED_PERSON_UUID,
new HsOfficePartnerEntityMatcher() HsOfficePartnerEntity::setPerson,
.withPatchedContactUuid(PATCHED_CONTACT_UUID) newPerson(PATCHED_PERSON_UUID))
.withPatchedPersonUuid(PATCHED_PERSON_UUID) .notNullable()
.withPatchedRegistrationOffice("patched Reg-Office") .resolvesUuid(),
.withPatchedRegistrationNumber("patched Reg-Number") new TestCase(
.withPatchedBirthday(PATCHED_BIRTHDAY) "registrationOffice",
.withPatchedBirthName("patched birth name") HsOfficePartnerPatchResource::setRegistrationOffice,
.withPatchedDateOfDeath(PATCHED_DAY_OF_DEATH) "patched Reg-Office",
.matches(givenPartner); HsOfficePartnerEntity::setRegistrationOffice),
new TestCase(
"birthday",
HsOfficePartnerPatchResource::setBirthday,
PATCHED_BIRTHDAY,
HsOfficePartnerEntity::setBirthday),
new TestCase(
"dayOfDeath",
HsOfficePartnerPatchResource::setDateOfDeath,
PATCHED_DATE_OF_DEATH,
HsOfficePartnerEntity::setDateOfDeath)
);
} }
@Test private static HsOfficeContactEntity newContact(final UUID uuid) {
void willThrowIfNoContactFound() {
// given
patchResource.setContactUuid(JsonNullable.of(null));
// when
final var exception = catchThrowableOfType(() -> {
hsOfficePartnerEntityPatch.apply(patchResource);
}, NoSuchElementException.class);
// then
assertThat(exception.getMessage()).isEqualTo("cannot find contact uuid null");
}
@Test
void willThrowIfNoPersonFound() {
// given
patchResource.setPersonUuid(JsonNullable.of(null));
// when
final var exception = catchThrowableOfType(() -> {
hsOfficePartnerEntityPatch.apply(patchResource);
}, NoSuchElementException.class);
// then
assertThat(exception.getMessage()).isEqualTo("cannot find person uuid null");
}
@Test
void willPatchOnlyContactProperty() {
// given
patchResource.setContactUuid(JsonNullable.of(PATCHED_CONTACT_UUID));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedContactUuid(PATCHED_CONTACT_UUID)
.matches(givenPartner);
}
@Test
void willPatchOnlyPersonProperty() {
// given
patchResource.setPersonUuid(JsonNullable.of(PATCHED_PERSON_UUID));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedPersonUuid(PATCHED_PERSON_UUID)
.matches(givenPartner);
}
@ParameterizedTest
@ValueSource(strings = { "patched Reg-Office" })
@NullSource
void willPatchOnlyRegOfficeProperty(final String patchedValue) {
// given
patchResource.setRegistrationOffice(JsonNullable.of(patchedValue));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedRegistrationOffice(patchedValue)
.matches(givenPartner);
}
@ParameterizedTest
@ValueSource(strings = { "patched birth name" })
@NullSource
void willPatchOnlyRegNumberProperty(final String patchedValue) {
// given
patchResource.setRegistrationNumber(JsonNullable.of(patchedValue));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedRegistrationNumber(patchedValue)
.matches(givenPartner);
}
@ParameterizedTest
@EnumSource(LocalDatePatches.class)
void willPatchOnlyBirthdayProperty(final LocalDatePatches patch) {
// given
patchResource.setBirthday(JsonNullable.of(patch.value));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedBirthday(patch.value)
.matches(givenPartner);
}
@ParameterizedTest
@ValueSource(strings = { "patched birth name" })
@NullSource
void willPatchOnlyBirthNameProperty(final String patchedValue) {
// given
patchResource.setBirthName(JsonNullable.of(patchedValue));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedBirthName(patchedValue)
.matches(givenPartner);
}
@ParameterizedTest
@EnumSource(LocalDatePatches.class)
void willPatchOnlyDateOfDeathProperty(final LocalDatePatches patch) {
// given
patchResource.setDateOfDeath(JsonNullable.of(patch.value));
// when
hsOfficePartnerEntityPatch.apply(patchResource);
// then
new HsOfficePartnerEntityMatcher()
.withPatchedDateOfDeath(patch.value)
.matches(givenPartner);
}
private HsOfficeContactEntity newContact(final UUID uuid) {
final var newContact = new HsOfficeContactEntity(); final var newContact = new HsOfficeContactEntity();
newContact.setUuid(uuid); newContact.setUuid(uuid);
return newContact; return newContact;
@ -233,75 +116,4 @@ class HsOfficePartnerEntityPatchUnitTest {
newPerson.setUuid(uuid); newPerson.setUuid(uuid);
return newPerson; return newPerson;
} }
private static class HsOfficePartnerEntityMatcher {
private UUID expectedContactUuid = INITIAL_CONTACT_UUID;
private UUID expectedPersonUuid = INITIAL_PERSON_UUID;
private String expectedRegOffice = "initial Reg-Office";
private String expectedRegNumber = "initial Reg-Number";
private LocalDate expectedBirthday = INITIAL_BIRTHDAY;
private String expectedBirthName = "initial birth name";
private LocalDate expectedDateOfDeath = INITIAL_DAY_OF_DEATH;
HsOfficePartnerEntityMatcher withPatchedContactUuid(final UUID patchedContactUuid) {
expectedContactUuid = patchedContactUuid;
return this;
}
HsOfficePartnerEntityMatcher withPatchedPersonUuid(final UUID patchedPersonUuid) {
expectedPersonUuid = patchedPersonUuid;
return this;
}
HsOfficePartnerEntityMatcher withPatchedRegistrationOffice(final String patchedRegOffice) {
expectedRegOffice = patchedRegOffice;
return this;
}
HsOfficePartnerEntityMatcher withPatchedRegistrationNumber(final String patchedRegNumber) {
expectedRegNumber = patchedRegNumber;
return this;
}
HsOfficePartnerEntityMatcher withPatchedBirthday(final LocalDate patchedBirthday) {
expectedBirthday = patchedBirthday;
return this;
}
HsOfficePartnerEntityMatcher withPatchedBirthName(final String patchedBirthName) {
expectedBirthName = patchedBirthName;
return this;
}
HsOfficePartnerEntityMatcher withPatchedDateOfDeath(final LocalDate patchedDayOfDeath) {
expectedDateOfDeath = patchedDayOfDeath;
return this;
}
void matches(final HsOfficePartnerEntity givenPartner) {
assertThat(givenPartner.getContact().getUuid()).isEqualTo(expectedContactUuid);
assertThat(givenPartner.getPerson().getUuid()).isEqualTo(expectedPersonUuid);
assertThat(givenPartner.getRegistrationOffice()).isEqualTo(expectedRegOffice);
assertThat(givenPartner.getRegistrationNumber()).isEqualTo(expectedRegNumber);
assertThat(givenPartner.getBirthday()).isEqualTo(expectedBirthday);
assertThat(givenPartner.getBirthName()).isEqualTo(expectedBirthName);
assertThat(givenPartner.getDateOfDeath()).isEqualTo(expectedDateOfDeath);
}
}
enum LocalDatePatches {
REAL_VALUE(LocalDate.now()),
NULL_VALUE(null);
final LocalDate value;
LocalDatePatches(final LocalDate patchedBirthday) {
value = patchedBirthday;
}
}
} }

View File

@ -0,0 +1,210 @@
package net.hostsharing.hsadminng.hs.office.partner;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.openapitools.jackson.nullable.JsonNullable;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
import static org.assertj.core.api.Assumptions.assumeThat;
public abstract class PatchUnitTestBase<R, E> {
@Test
void willPatchNoProperty() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
// when
createPatcher(givenEntity).apply(patchResource);
// then
final var expectedEntity = newInitialEntity();
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(expectedEntity);
}
@Test
void willPatchAllProperties() {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
testCases().forEach(testCase ->
testCase.resourceSetter.accept(patchResource, JsonNullable.of(testCase.givenPatchedValue))
);
// when
createPatcher(givenEntity).apply(patchResource);
// then
final var expectedEntity = newInitialEntity();
testCases().forEach(testCase ->
testCase.entitySetter.accept(expectedEntity, testCase.expectedPatchValue)
);
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(expectedEntity);
}
@ParameterizedTest
@MethodSource("testCaseArguments")
void willPatchOnlyGivenProperty(final TestCase testCase) {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
testCase.resourceSetter.accept(patchResource, JsonNullable.of(testCase.givenPatchedValue));
// when
createPatcher(givenEntity).apply(patchResource);
// then
final var expectedEntity = newInitialEntity();
testCase.entitySetter.accept(expectedEntity, testCase.expectedPatchValue);
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(expectedEntity);
}
@ParameterizedTest
@MethodSource("testCaseArguments")
void willThrowIfUUidCannotBeResolved(final TestCase testCase) {
assumeThat(testCase.resolvesUuid).isTrue();
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
final var givenPatchValue = UUID.fromString("11111111-1111-1111-1111-111111111111");
testCase.resourceSetter.accept(patchResource, JsonNullable.of(givenPatchValue));
// when
final var exception = catchThrowableOfType(() -> {
createPatcher(givenEntity).apply(patchResource);
}, NoSuchElementException.class);
// then
assertThat(exception).isInstanceOf(NoSuchElementException.class)
.hasMessage("cannot find '" + testCase.name + "' uuid " + givenPatchValue);
}
@ParameterizedTest
@MethodSource("testCaseArguments")
void willPatchOnlyGivenPropertyToNull(final TestCase testCase) {
assumeThat(testCase.nullable).isTrue();
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
testCase.resourceSetter.accept(patchResource, JsonNullable.of(null));
// when
createPatcher(givenEntity).apply(patchResource);
// then
final var expectedEntity = newInitialEntity();
testCase.entitySetter.accept(expectedEntity, null);
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(expectedEntity);
}
@ParameterizedTest
@MethodSource("testCaseArguments")
void willThrowExceptionIfResourcePropertyIsNull(final TestCase testCase) {
assumeThat(testCase.nullable).isFalse();
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
testCase.resourceSetter.accept(patchResource, JsonNullable.of(null));
// when
final var actualException = catchThrowableOfType(
() -> createPatcher(givenEntity).apply(patchResource),
IllegalArgumentException.class);
// then
assertThat(actualException).hasMessage("property '" + testCase.name + "' must not be null");
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(newInitialEntity());
}
@ParameterizedTest
@MethodSource("testCaseArguments")
void willNotPatchIfGivenPropertyNotGiven(final TestCase testCase) {
// given
final var givenEntity = newInitialEntity();
final var patchResource = newPatchResource();
testCase.resourceSetter.accept(patchResource, null);
// when
createPatcher(givenEntity).apply(patchResource);
// then
final var expectedEntity = newInitialEntity();
assertThat(givenEntity).usingRecursiveComparison().isEqualTo(expectedEntity);
}
abstract E newInitialEntity();
abstract R newPatchResource();
abstract EntityPatch<R> createPatcher(final E entity);
abstract Stream<TestCase> testCases();
Stream<Arguments> testCaseArguments() {
return testCases().map(tc -> Arguments.of(Named.of(tc.name, tc)));
}
class TestCase {
private final String name;
public final Object givenPatchedValue;
private final BiConsumer<Object, JsonNullable<?>> resourceSetter;
private final BiConsumer<Object, Object> entitySetter;
private final Object expectedPatchValue;
private boolean nullable = true;
private boolean resolvesUuid = false;
<R, V, E> TestCase(
final String name,
final BiConsumer<R, JsonNullable<V>> resourceSetter,
final V givenPatchValue,
final BiConsumer<E, V> entitySetter
) {
this.name = name;
this.resourceSetter = (BiConsumer<Object, JsonNullable<?>>) (BiConsumer) resourceSetter;
this.givenPatchedValue = givenPatchValue;
this.entitySetter = (BiConsumer<Object, Object>) entitySetter;
this.expectedPatchValue = givenPatchValue;
}
<R, V, E, S> TestCase(
final String name,
final BiConsumer<R, JsonNullable<V>> resourceSetter,
final V givenPatchValue,
final BiConsumer<E, S> entitySetter,
final S expectedPatchValue
) {
this.name = name;
this.resourceSetter = (BiConsumer<Object, JsonNullable<?>>) (BiConsumer) resourceSetter;
this.givenPatchedValue = givenPatchValue;
this.entitySetter = (BiConsumer<Object, Object>) entitySetter;
this.expectedPatchValue = expectedPatchValue;
}
TestCase notNullable() {
nullable = false;
return this;
}
TestCase resolvesUuid() {
resolvesUuid = true;
return this;
}
}
}