Add unit tests for InventoryMutateForm using jqwik

This adds the jqwik dependency for property based testing, like
Haskell’s QuickCheck.
inventory-tests2
Simon Bruder 2024-01-08 14:09:40 +01:00
parent da19356496
commit 0c5e3f4e5c
Signed by: simon
GPG Key ID: 8D3C82F9F309F8EC
3 changed files with 276 additions and 0 deletions

3
.gitignore vendored
View File

@ -60,3 +60,6 @@ hs_err_*.log
*.synctex.gz
*.toc
*.xdv
# jqwik
.jqwik-database

View File

@ -58,6 +58,13 @@ SPDX-FileCopyrightText: 2023 swt23w23
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.jqwik</groupId>
<artifactId>jqwik</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>

View File

@ -0,0 +1,266 @@
// 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);
}
}