ปัญหาเรื่องหนึ่งสำหรับการทำ Unit testing ก็คือ เรื่อง DateTime มันเป็นยังไง มาดูกัน ผมสมมุติสถานะการณ์แบบนี้..
กรณีลูกค้าสั่ง Order มาแล้วเกิน 7 วันลูกค้าจะไม่สามารถ Reject Order นั้นๆได้
public class Order { public virtual OrderStatusType OrderStatus { get; protected set; } public virtual DateTime OrderedDate { get; private set; } public virtual DateTime? RejectedDate { get; private set; } public Order() { //Set current ordered date //this.OrderedDate = SystemTime.Now; this.OrderedDate = DateTime.Now; this.OrderStatus = OrderStatusType.New; } public virtual bool IsRejected() { return this.OrderStatus == OrderStatusType.Reject; } public virtual void MaskAsReject() { //Set current reject date //var rejectedDate = SystemTime.Now; var rejectedDate = DateTime.Now; this.ValidateCanRejectOrder(rejectedDate); this.OrderStatus = OrderStatusType.Reject; this.RejectedDate = rejectedDate; } private void ValidateCanRejectOrder(DateTime rejectedDate) { // Validate Order Status can reject order here.. //... var canRejectOrder = rejectedDate.Subtract(this.OrderedDate).Days < 7; if (!canRejectOrder) throw new InvalidDateForRejectOrderException(); } }
ปัญหาคือ DateTime.Now มันจะดึงเวลามาจาก System ทำให้เรา test ยาก ทางแก้คือ เราต้องปรับ Code จากเดิมที่ใช้ DateTime.Now ไปใช้ SystemTime.Now แทนครับ ซึ่งผมได้ Comment ไว้ด้านบน ส่วน Code Implement ได้ตามด้านล่างนี้เลยครับ
public static class SystemTime { public static Func<DateTime> SetCurrentTime = () => DateTime.Now; public static DateTime Now { get { return SetCurrentTime(); } } }
เท่านี้ เราก็สามารถกำหนด Expected Date ตามที่เราต้องการใน Unit test ของเราได้แล้วครับ
[TestClass] public class OrderTests { private Order GetOrder() { var orderedDate = new DateTime(2014, 01, 01); var scope = new Mock<Order>() { CallBase = true }; scope.Setup(x => x.OrderedDate).Returns(orderedDate); scope.SetupProperty(x => x.OrderStatus, OrderStatusType.New); return scope.Object; } [TestMethod] public void MaskAsReject_OnSuccess_OrderShouldBeRejected() { //Arrange var scope = this.GetOrder(); var rejectedDate = scope.OrderedDate.AddDays(6); SystemTime.SetCurrentTime = () => rejectedDate; //Act scope.MaskAsReject(); //Assert Assert.IsTrue(scope.IsRejected()); Assert.AreEqual(rejectedDate, scope.RejectedDate); } [TestMethod, ExpectedException(typeof(InvalidDateForRejectOrderException))] public void MaskAsReject_OnOrderedDateIsGreaterThan7Days_ThrowInvalidDateForRejectOrderException() { //Arrange var scope = this.GetOrder(); SystemTime.SetCurrentTime = () => scope.OrderedDate.AddDays(7); //Act scope.MaskAsReject(); } }
SystemTime สามารถ Implement ได้อีกแบบ ที่นี่ แต่ที่ผมเลือกใช้แบบด้านบน เพราะมันสามารถเอามาแทน DateTime ของ .Net ได้เลย ซึ่งจะไม่ Error กรณีที่เราต้องการใช้ LINQ Query ข้อมูลจาก Database ครับ Happy coding ครับ 🙂
tapez@555
You must unlearn what you have learned.