From 5407cc4700e3bc997f46a33746472beb350c3128 Mon Sep 17 00:00:00 2001 From: Seongbin Kim Date: Sun, 30 Jan 2022 16:01:37 +0900 Subject: [PATCH] Implement Order Domain Objects - Cart, CartItem, CartItemOption (#1) --- src/AUSG.Eats.Order.Domain/Cart.cs | 44 ++++++ src/AUSG.Eats.Order.Domain/CartItem.cs | 28 ++++ src/AUSG.Eats.Order.Domain/CartItemOption.cs | 13 ++ src/AUSG.Eats.Order.Test/OrderTests.cs | 143 +++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/AUSG.Eats.Order.Domain/Cart.cs create mode 100644 src/AUSG.Eats.Order.Domain/CartItem.cs create mode 100644 src/AUSG.Eats.Order.Domain/CartItemOption.cs create mode 100644 src/AUSG.Eats.Order.Test/OrderTests.cs diff --git a/src/AUSG.Eats.Order.Domain/Cart.cs b/src/AUSG.Eats.Order.Domain/Cart.cs new file mode 100644 index 0000000..2abd17f --- /dev/null +++ b/src/AUSG.Eats.Order.Domain/Cart.cs @@ -0,0 +1,44 @@ +namespace AUSG.Eats.Order.Domain; + +public class Cart +{ + public long UserId { get; set; } + + // IList에는 AsReadOnly();가 없다. (why?) + private readonly List _items = new List(); + + // IReadOnlyCollection에서 hint로 IEnumerable로 변경함 + // hint로 Expression Body로 변경함 + public IEnumerable Items => this._items.AsReadOnly(); + + public void AddToCart(CartItem newItem) + { + this._items.Add(newItem); + } + + public void RemoveFromCart(CartItem itemToRemove) + { + this._items.Remove(itemToRemove); + } + + public void AlterCartItem(CartItem itemToChange) + { + if (!this._items.Remove(itemToChange)) // Id 비교이므로 제거된다. + throw new ArgumentException("없는 CartItem을 변경하려고 한다."); + this._items.Add(itemToChange); + } + + public override bool Equals(object? obj) + { + if ((obj == null) || !(this.GetType() == obj.GetType())) + return false; + var another = (Cart) obj; + return this.UserId == another.UserId; + } + + public override int GetHashCode() + { + // Non-readonly property referenced in 'GetHashCode()' ?? + return this.UserId.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/AUSG.Eats.Order.Domain/CartItem.cs b/src/AUSG.Eats.Order.Domain/CartItem.cs new file mode 100644 index 0000000..12df1aa --- /dev/null +++ b/src/AUSG.Eats.Order.Domain/CartItem.cs @@ -0,0 +1,28 @@ +namespace AUSG.Eats.Order.Domain; + +public class CartItem +{ + public long Id { get; set; } + public List Options { get; set; } + public int Quantity { get; set; } + + public CartItem(List options, int quantity) + { + this.Options = options; + this.Quantity = quantity; + } + + public override bool Equals(object? obj) + { + if ((obj == null) || !(this.GetType() == obj.GetType())) + return false; + var another = (CartItem) obj; + return this.Id == another.Id; + } + + public override int GetHashCode() + { + // Non-readonly property referenced in 'GetHashCode()' ?? + return this.Id.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/AUSG.Eats.Order.Domain/CartItemOption.cs b/src/AUSG.Eats.Order.Domain/CartItemOption.cs new file mode 100644 index 0000000..9c27fc0 --- /dev/null +++ b/src/AUSG.Eats.Order.Domain/CartItemOption.cs @@ -0,0 +1,13 @@ +namespace AUSG.Eats.Order.Domain; + +public class CartItemOption +{ + public string Name { get; } + public decimal Price { get; } + + public CartItemOption(string name, decimal price) + { + this.Name = name; + this.Price = price; + } +} diff --git a/src/AUSG.Eats.Order.Test/OrderTests.cs b/src/AUSG.Eats.Order.Test/OrderTests.cs new file mode 100644 index 0000000..27002d1 --- /dev/null +++ b/src/AUSG.Eats.Order.Test/OrderTests.cs @@ -0,0 +1,143 @@ +using System.Collections.ObjectModel; +using AUSG.Eats.Order.Domain; +using Xunit; + +namespace AUSG.Eats.Order.Test; + +public class OrderTests +{ + + private CartItem MakeNewCartItem() + { + var options = new List(); + const int quantity = 1; + return new CartItem(options, quantity); + } + + [Fact(DisplayName = "CartItemOption 객체는 Name과 Price 필드를 노출한다.")] + public void CartItemOption_Shares_Name_and_Price() + { + const string name = "name"; + const decimal price = 1000m; + var cartItemOption = new CartItemOption(name, price); + + Assert.Equal(cartItemOption.Name, name); + Assert.Equal(cartItemOption.Price, price); + } + + [Fact(DisplayName = "CartItem 객체는 Id, List, Quantity 필드를 노출한다.")] + public void CartItem_Shares_Id_and_ListOfCartItemOption_and_Quantity() + { + const long pid = 1L; + var options = new List(); + const int quantity = 0; + var cartItem = new CartItem(options, quantity); + cartItem.Id = pid; + + Assert.Equal(cartItem.Options, options); // IEnumerable ? + Assert.Equal(cartItem.Quantity, quantity); + Assert.Equal(cartItem.Id, pid); + } + + [Fact(DisplayName = "Cart 객체는 List 필드를 노출한다.")] + public void Cart_Shares_ListOfCartItem() + { + var cart = new Cart(); + Assert.Empty(cart.Items); // Collection && size=0 + } + + [Fact(DisplayName = "Cart 객체가 노출하는 List은 외부에서 그 내용을 수정할 수 없어야 한다.")] + public void ListOfCartItem_of_Cart_Must_Not_Be_Mutable() + { + var cart = new Cart(); + + Assert.True(cart.Items is ReadOnlyCollection); + } + + [Fact(DisplayName = "Cart 객체는 UserId 필드를 노출하며 UserId로 식별할 수 있다.")] + public void Cart_Shares_UserId_and_Can_be_Identified_Using_UserId() + { + var cart1 = new Cart(); + var cart2 = new Cart(); + var userId = 1L; + cart1.UserId = userId; + cart2.UserId = userId; + + // Shares Id + Assert.Equal(cart1.UserId, userId); + + // Identified by UserId + Assert.Equal(cart1, cart2); + + // Compare HashCode + Assert.Equal(cart1.GetHashCode(), cart2.GetHashCode()); + } + + [Fact(DisplayName = "Cart 객체는 CartItem을 추가할 수 있다.")] + public void Cart_can_Add_CartItem() + { + var cart = new Cart(); + var cartItem1 = MakeNewCartItem(); + cart.AddToCart(cartItem1); + + Assert.Equal(cart.Items.ElementAt(0), cartItem1); + } + + [Fact(DisplayName = "Cart 객체는 CartItem을 제거할 수 있다.")] + public void Cart_can_Remove_CartItem() + { + // given + var cart = new Cart(); + var cartItem1 = MakeNewCartItem(); + cart.AddToCart(cartItem1); + Assert.Equal(cart.Items.ElementAt(0), cartItem1); + + // when + cart.RemoveFromCart(cartItem1); + + // then + Assert.Empty(cart.Items); + } + + [Fact(DisplayName = "Cart 객체는 CartItem을 변경할 수 있다.")] + public void Cart_can_Alter_CartItem() + { + // given + const long cartItemId = 1L; + var cart = new Cart(); + var oldItem = MakeNewCartItem(); + oldItem.Id = cartItemId; + cart.AddToCart(oldItem); + + // when (Item의 상태를 변경함) + // 가능한 모든 필드가 변경됨을 확인하는 게 적절할 것으로 보임 + var newOptions = new List(); + const int newQuantity = 2; + oldItem.Options = newOptions; + oldItem.Quantity = newQuantity; + cart.AlterCartItem(oldItem); + + // then + var alteredItem = cart.Items.ElementAt(0); + Assert.Equal(alteredItem.Id, oldItem.Id); + Assert.Equal(alteredItem.Options, newOptions); // Collection의 새 레퍼런스의 일치 확인해도 충분하다. + Assert.Equal(alteredItem.Quantity, newQuantity); + } + + // 추가 TC + [Fact(DisplayName = "Cart 객체는 CartItem을 변경할 때 해당 Item이 이미 List에 없다면 예외를 발생시킨다.")] + public void Cart_throw_ArgumentException_when_Alter_CartItem() + { + // given + var cart = new Cart(); + var oldItem = MakeNewCartItem(); + oldItem.Id = 1L; + + // when (Item의 상태를 변경함) + // 가능한 모든 필드가 변경됨을 확인하는 게 적절할 것으로 보임 + Action alterItem = () => cart.AlterCartItem(oldItem); + + // then + ArgumentException ex = Assert.Throws(alterItem); + } +}