Skip to the content.
package com.bookshop.creational.builder;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.bookshop.domain.Book;
import com.bookshop.domain.Customer;
import java.math.BigDecimal;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Builder -- assembling an Order readably, with validation in one place")
class OrderBuilderTest {

    private final Customer alice = new Customer("c-1", "Alice", "alice@example.com", 3);
    private final Book refactoring = new Book("978-0134757599", "Refactoring", "Martin Fowler",
            new BigDecimal("47.99"));
    private final Book effectiveJava = new Book("978-0134685991", "Effective Java", "Joshua Bloch",
            new BigDecimal("39.99"));

    @Test
    @DisplayName("call sites read like a sentence instead of a null-riddled constructor")
    void buildsFullOrderFluently() {
        Order order = Order.forCustomer(alice)
                .add(refactoring)
                .add(effectiveJava)
                .giftMessage("Happy birthday!")
                .express()
                .build();

        assertEquals(alice, order.customer());
        assertEquals(2, order.books().size());
        assertEquals("Happy birthday!", order.giftMessage().orElseThrow());
        assertTrue(order.expressDelivery());
        assertTrue(order.promoCode().isEmpty());
        assertEquals(new BigDecimal("87.98"), order.subtotal());
    }

    @Test
    @DisplayName("optional fields can simply be omitted -- no telescoping overloads needed")
    void omittedOptionalsGetSafeDefaults() {
        Order order = Order.forCustomer(alice).add(refactoring).build();

        assertTrue(order.giftMessage().isEmpty());
        assertTrue(order.deliveryInstructions().isEmpty());
        assertEquals(false, order.expressDelivery());
    }

    @Test
    @DisplayName("build() is the single validation point -- invalid orders never exist")
    void rejectsEmptyOrder() {
        Order.Builder builder = Order.forCustomer(alice);

        assertThrows(IllegalStateException.class, builder::build);
    }

    @Test
    @DisplayName("the built order is immutable -- its book list cannot be modified")
    void builtOrderIsImmutable() {
        Order order = Order.forCustomer(alice).add(refactoring).build();

        assertThrows(UnsupportedOperationException.class, () -> order.books().add(effectiveJava));
    }
}

Raw file