Wzorzec projektowy ObjectMother to technika używana w testach jednostkowych, która pomaga w generowaniu obiektów testowych. W tym wpisie przedstawię, jak można zastosować ten wzorzec w języku Kotlin.
Wzorzec najlepiej sprawdza się w dużych projektach z wieloma testami, które używają podobnych obiektów.
data class Customer(val id: Int, val name: String, val email: String)
data class Product(val id: Int, val name: String, val price: Double)
data class Order(val id: Int, val customer: Customer, val products: List<Product>)
package pro.adamski.objectMother
import kotlin.random.Random
object ObjectMother {
fun customer(
id: Int = Random.nextInt(),
name: String = "John Doe${Random.nextInt()}",
email: String = "info-${Random.nextInt()}@example.com"
): Customer {
return Customer(id, name, email)
}
fun product(
id: Int = Random.nextInt(),
name: String = "The ${Random.nextInt()}th laptop",
price: Double = Random.nextDouble(1000.0, 5000.0)
): Product {
return Product(id, name, price)
}
fun order(
id: Int = Random.nextInt(),
customer: Customer = customer(),
products: List<Product> = listOf(product())
): Order {
return Order(id, customer, products)
}
fun largeOrder(id: Int = Random.nextInt(), customer: Customer = customer()): Order {
val products = List(100) { product(id = it, name = "Product$it") }
return Order(id, customer, products)
}
}
package pro.adamski.objectMother;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class ObjectMotherJava {
private static final Random random = new Random();
public static CustomerBuilder customer() {
return new CustomerBuilder()
.withId(random.nextInt())
.withName("John Doe" + random.nextInt())
.withEmail("info-" + random.nextInt() + "@example.com");
}
public static ProductBuilder product() {
return new ProductBuilder()
.withId(random.nextInt())
.withName("The " + random.nextInt() + "th laptop")
.withPrice(random.nextDouble() * (5000.0 - 1000.0) + 1000.0);
}
public static OrderBuilder order() {
return new OrderBuilder()
.withId(random.nextInt())
.withCustomer(customer().build())
.addProduct(product().build());
}
public static Order largeOrder() {
OrderBuilder orderBuilder = new OrderBuilder()
.withId(random.nextInt())
.withCustomer(customer().build());
for (int i = 0; i < 100; i++) {
orderBuilder.addProduct(product().withId(i).withName("Product" + i).build());
}
return orderBuilder.build();
}
}
Pełny kod fluent builderami na GitHub
package pro.adamski.objectMother
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class OrderTest {
@Test
fun testCustomOrder() {
// Given
val customProduct = ObjectMother.product(name = "Custom Laptop", price = 2000.0)
// When
val customOrder = ObjectMother.order(products = listOf(customProduct))
// Then
assertOrderProduct(customOrder.products.first(), "Custom Laptop", 2000.0)
}
@Test
fun testRandomLargeOrder() {
// When
val largeOrder = ObjectMother.largeOrder()
// Then
assertOrderProducts(largeOrder, 100, 1000.0..5000.0)
}
@Test
fun testCustomOrderJava() {
// Given
val customProduct = ObjectMotherJava.product().withName("Custom Laptop").withPrice(2000.0).build()
// When
val customOrder = ObjectMotherJava.order().withProducts(listOf(customProduct)).build()
// Then
assertOrderProduct(customOrder.products.first(), "Custom Laptop", 2000.0)
}
@Test
fun testRandomLargeOrderJava() {
// When
val largeOrder = ObjectMotherJava.largeOrder()
// Then
assertOrderProducts(largeOrder, 100, 1000.0..5000.0)
}
private fun assertOrderProducts(
largeOrder: Order,
numberOfProducts: Int,
priceRange: ClosedFloatingPointRange<Double>
) {
assertThat(largeOrder.products).hasSize(numberOfProducts)
assertThat(largeOrder.products).allMatch {
it.price in priceRange
}
}
private fun assertOrderProduct(firstProduct: Product, name: String, price: Double) {
assertThat(firstProduct.name).isEqualTo(name)
assertThat(firstProduct.price).isEqualTo(price)
}
}
Wzorzec ObjectMother jest bardzo użyteczny, zwłaszcza w dużych projektach z rozbudowaną bazą testów. Pomaga w utrzymaniu czystego i efektywnego kodu testowego. W przypadku implementacji w Javie zastosowanie fluent builderów pozwala na jeszcze większą czytelność kodu testowego.