DateTime and Unit testing

ปัญหาเรื่องหนึ่งสำหรับการทำ 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.

Leave a comment