swt23w23/src/test/java/catering/inventory/InventoryMutateFormUnitTests.java
Simon Bruder 0c5e3f4e5c
Add unit tests for InventoryMutateForm using jqwik
This adds the jqwik dependency for property based testing, like
Haskell’s QuickCheck.
2024-01-14 17:05:58 +01:00

267 lines
9.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 swt23w23
package catering.inventory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.salespointframework.core.Currencies.EURO;
import java.util.Optional;
import java.util.Set;
import javax.money.MonetaryAmount;
import javax.money.NumberValue;
import org.javamoney.moneta.Money;
import org.junit.jupiter.api.Test;
import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Metric;
import org.salespointframework.quantity.Quantity;
import catering.catalog.Consumable;
import catering.catalog.Rentable;
import catering.order.OrderType;
import net.jqwik.api.Arbitraries;
import net.jqwik.api.Arbitrary;
import net.jqwik.api.Assume;
import net.jqwik.api.Combinators;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.Provide;
import net.jqwik.api.constraints.NotBlank;
public class InventoryMutateFormUnitTests {
/*
* Providers for jqwik
*/
@Provide
Arbitrary<MonetaryAmount> euroMonetaryAmount() {
return Arbitraries.doubles()
.filter(amount -> amount >= 0)
.map(amount -> Money.of(amount, EURO));
}
@Provide
Arbitrary<Optional<MonetaryAmount>> optionalEuroMonetaryAmount() {
return euroMonetaryAmount().optional();
}
@Provide
Arbitrary<Long> positiveOrZero() {
return Arbitraries.longs().filter(x -> x >= 0);
}
@Provide
Arbitrary<Consumable> consumable() {
return Combinators
.combine(Arbitraries.strings().filter(s -> !s.isBlank()), euroMonetaryAmount(), euroMonetaryAmount(),
optionalEuroMonetaryAmount(), Arbitraries.subsetOf(OrderType.values()),
Arbitraries.of(Metric.class))
.as((name, retailPrice, wholesalePrice, promotionPrice, orderTypes, metric) -> new Consumable(name,
retailPrice, wholesalePrice, promotionPrice, orderTypes, metric));
}
@Provide
Arbitrary<Rentable> rentable() {
return Combinators
.combine(Arbitraries.strings().filter(s -> !s.isBlank()), euroMonetaryAmount(), euroMonetaryAmount(),
Arbitraries.subsetOf(OrderType.values()),
Arbitraries.of(Metric.class))
.as((name, retailPrice, wholesalePrice, orderTypes, metric) -> new Rentable(name, retailPrice,
wholesalePrice, orderTypes, metric));
}
Arbitrary<UniqueInventoryItem> item(Arbitrary<? extends Product> product) {
return Combinators.combine(product, positiveOrZero())
.as((prod, qty) -> new UniqueInventoryItem(prod, prod.createQuantity(qty)));
}
@Provide
Arbitrary<UniqueInventoryItem> consumableItem() {
return item(consumable());
}
@Provide
Arbitrary<UniqueInventoryItem> rentableItem() {
return item(rentable());
}
/*
* Property-based tests with jqwik
*/
@Property
void property_formToConsumableToFormIsIdentity(@ForAll @NotBlank String name,
@ForAll("positiveOrZero") long qty,
@ForAll Metric metric,
@ForAll("euroMonetaryAmount") MonetaryAmount retailPrice,
@ForAll("euroMonetaryAmount") MonetaryAmount wholesalePrice,
@ForAll("optionalEuroMonetaryAmount") Optional<MonetaryAmount> promotionPrice,
@ForAll Set<OrderType> orderTypes) {
ConsumableMutateForm form = new ConsumableMutateForm();
Assume.that(form.supportedMetrics().contains(metric));
form.setName(name);
form.setQuantity(qty);
form.setMetric(metric);
form.setRetailPrice(retailPrice.getNumber().doubleValueExact());
form.setWholesalePrice(retailPrice.getNumber().doubleValueExact());
form.setPromotionPrice(promotionPrice.map(MonetaryAmount::getNumber).map(NumberValue::doubleValueExact));
form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE));
Product product = form.toProduct();
UniqueInventoryItem item = new UniqueInventoryItem(product, product.createQuantity(qty));
assertThat(ConsumableMutateForm.of(item))
.usingRecursiveComparison()
.isEqualTo(form);
}
@Property
void property_formToRentableToFormIsIdentity(@ForAll @NotBlank String name,
@ForAll("positiveOrZero") long qty,
@ForAll Metric metric,
@ForAll("euroMonetaryAmount") MonetaryAmount retailPrice,
@ForAll("euroMonetaryAmount") MonetaryAmount wholesalePrice,
@ForAll Set<OrderType> orderTypes) {
RentableMutateForm form = new RentableMutateForm();
Assume.that(form.supportedMetrics().contains(metric));
form.setName(name);
form.setQuantity(qty);
form.setMetric(metric);
form.setRetailPrice(retailPrice.getNumber().doubleValueExact());
form.setWholesalePrice(retailPrice.getNumber().doubleValueExact());
form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE));
Product product = form.toProduct();
UniqueInventoryItem item = new UniqueInventoryItem(product, product.createQuantity(qty));
assertThat(RentableMutateForm.of(item))
.usingRecursiveComparison()
.isEqualTo(form);
}
@Property
void property_consumableToFormToConsumableIsIdentity(@ForAll("consumableItem") UniqueInventoryItem item) {
InventoryMutateForm form = InventoryMutateForm.of(item);
assertThat(form).isInstanceOf(ConsumableMutateForm.class);
assertThat(form.toProduct())
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(item.getProduct());
assertThat(form.getQuantity()).isEqualTo(item.getQuantity().getAmount().longValueExact());
}
@Property
void property_rentableToFormToRentableIsIdentity(@ForAll("rentableItem") UniqueInventoryItem item) {
InventoryMutateForm form = InventoryMutateForm.of(item);
assertThat(form).isInstanceOf(RentableMutateForm.class);
assertThat(form.toProduct())
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(item.getProduct());
assertThat(form.getQuantity()).isEqualTo(item.getQuantity().getAmount().longValueExact());
}
@Property
void property_consumableFormModifiesExistingConsumable(@ForAll("consumableItem") UniqueInventoryItem item,
@ForAll("consumable") Consumable product) {
InventoryMutateForm.of(item).modifyProduct(product);
assertThat(product)
.usingRecursiveComparison()
.ignoringFields("id")
.ignoringFields("metric") // metric cant be modified (salespoint limitation)
.isEqualTo(item.getProduct());
}
@Property
void property_rentableFormModifiesExistingConsumable(@ForAll("rentableItem") UniqueInventoryItem item,
@ForAll("rentable") Rentable product) {
InventoryMutateForm.of(item).modifyProduct(product);
assertThat(product)
.usingRecursiveComparison()
.ignoringFields("id")
.ignoringFields("metric") // metric cant be modified (salespoint limitation)
.isEqualTo(item.getProduct());
}
/*
* Edge cases with jqwik
*/
@Property
void property_ofIllegal(@ForAll("consumableItem") UniqueInventoryItem consumableItem,
@ForAll("rentableItem") UniqueInventoryItem rentableItem) {
assertThatThrownBy(() -> InventoryMutateForm
.of(new UniqueInventoryItem(new Product("no subclass", Money.of(0.0, EURO)), Quantity.of(1))))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> ConsumableMutateForm.of(rentableItem))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> RentableMutateForm.of(consumableItem))
.isInstanceOf(IllegalArgumentException.class);
}
@Property
void property_modifyIllegalProduct(@ForAll("consumableItem") UniqueInventoryItem consumableItem,
@ForAll("rentableItem") UniqueInventoryItem rentableItem,
@ForAll("consumable") Consumable consumable,
@ForAll("rentable") Rentable rentable) {
assertThatThrownBy(() -> InventoryMutateForm.of(rentableItem).modifyProduct(consumable))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> InventoryMutateForm.of(consumableItem).modifyProduct(rentable))
.isInstanceOf(IllegalArgumentException.class);
}
/*
* JUnit Unit tests
*/
@Test
void toConsumable() {
ConsumableMutateForm form = new ConsumableMutateForm();
form.setName("Weißwurst (vegan)");
form.setQuantity(10L);
form.setMetric(Metric.UNIT);
form.setRetailPrice(3.46);
form.setWholesalePrice(1.50);
form.setPromotionPrice(Optional.of(3.33));
form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE));
assertThat(form.getQuantity()).isEqualTo(10L);
assertThat(form.toProduct())
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(new Consumable("Weißwurst (vegan)", Money.of(3.46, EURO), Money.of(1.50, EURO),
Optional.of(Money.of(3.33, EURO)), Set.of(OrderType.PARTY_SERVICE), Metric.UNIT));
}
@Test
void toRentable() {
RentableMutateForm form = new RentableMutateForm();
form.setName("Kebab-Drehspieß „Der Gerät“ Alkadur");
form.setQuantity(3L);
form.setMetric(Metric.UNIT);
form.setRetailPrice(20.0);
form.setWholesalePrice(10000.0);
form.setOrderTypes(Set.of(OrderType.EVENT_CATERING));
assertThat(form.getQuantity()).isEqualTo(3L);
assertThat(form.toProduct())
.usingRecursiveComparison()
.ignoringFields("id")
.isEqualTo(new Rentable("Kebab-Drehspieß „Der Gerät“ Alkadur", Money.of(20.0, EURO),
Money.of(10000.0, EURO), Set.of(OrderType.EVENT_CATERING), Metric.UNIT));
}
@Test
void forProductType() {
assertThat(InventoryMutateForm.forProductType(Consumable.class)).isInstanceOf(ConsumableMutateForm.class);
assertThat(InventoryMutateForm.forProductType(Rentable.class)).isInstanceOf(RentableMutateForm.class);
assertThatThrownBy(() -> InventoryMutateForm.forProductType(Product.class)).isInstanceOf(IllegalArgumentException.class);
}
}