Monday, February 27, 2012

A primer on unit testing with Moq

The following code is a primer on unit testing, just get Moq from Nuget. Note: Actual unit testing should be done on a separate project, not on Main method :-) This code just demonstrates how elegant Moq is for unit testing needs.

using System;

using Moq;

namespace MoqSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // ARRANGE
            
            decimal amount = 100;

            // that's why you will love unit testing, mishaps could be avoided :-)
            string errorMessage = "Could be an anomaly, passed amount is different from amount to transfer"; 

            var mockedSource = new Mock<IAccount>();
            mockedSource.Setup(x => x.Withdraw(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
            mockedSource.Setup(x => x.Deposit(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
            
            
            var mockedDest = new Mock<IAccount>();
            mockedDest.Setup(x => x.Withdraw(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
            mockedDest.Setup(x => x.Deposit(It.Is<decimal>(a => a != amount))).Throws(new Exception(errorMessage));
            



            // ACT

            int test = 0;
            /*
             * 0's error: NONE
             * 
             * 1's error:
             * Expected invocation on the mock should never have been performed, but was 1 times: source => source.Deposit(.amount)
             *  
             * 2's error:
             * Expected invocation on the mock once, but was 2 times: dest => dest.Deposit(.amount)
             * 
             * 3's error:
             * Expected invocation on the mock once, but was 0 times: dest => dest.Deposit(.amount)
             * 
             * 4's error:
             * Expected invocation on the mock once, but was 0 times: source => source.Withdraw(.amount)
             * 
             * 5's error:
             * Could be an anomaly, passed amount is different from transfer amount
             *             
             * */

            if (test == 0)
                TransactionMaker.TransferFund(mockedSource.Object, mockedDest.Object, amount);                
            else if (test == 1)
                TransactionMaker.TransferFundBuggy1(mockedSource.Object, mockedDest.Object, amount);                
            else if (test == 2)
                TransactionMaker.TransferFundBuggy2(mockedSource.Object, mockedDest.Object, amount);
            else if (test == 3)
                TransactionMaker.TransferFundBuggy3(mockedSource.Object, mockedDest.Object, amount);
            else if (test == 4)
                TransactionMaker.TransferFundBuggy4(mockedSource.Object, mockedDest.Object, amount);                
            else if (test == 5)
                TransactionMaker.TransferFundAnomalous(mockedSource.Object, mockedDest.Object, amount);                
            else
                throw new Exception("Select test case from 0 to 5");


            // ASSERT

            mockedSource.Verify(source => source.Withdraw(amount), Times.Once());
            mockedSource.Verify(source => source.Deposit(amount), Times.Never());

            mockedDest.Verify(dest => dest.Deposit(amount), Times.Once());
            mockedDest.Verify(dest => dest.Withdraw(amount), Times.Never());

        }

        
    }


    public static class TransactionMaker
    {

        public static void TransferFund(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Withdraw(amountToTransfer);
            destAccount.Deposit(amountToTransfer);            
        }

        public static void TransferFundBuggy1(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Withdraw(amountToTransfer);
            sourceAccount.Deposit(amountToTransfer);            
        }

        public static void TransferFundBuggy2(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Withdraw(amountToTransfer);
            destAccount.Deposit(amountToTransfer);
            destAccount.Deposit(amountToTransfer);
        }


        public static void TransferFundBuggy3(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Withdraw(amountToTransfer);            
        }

        public static void TransferFundBuggy4(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Deposit(amountToTransfer);
        }

        public static void TransferFundAnomalous(IAccount sourceAccount, IAccount destAccount, decimal amountToTransfer)
        {
            sourceAccount.Withdraw(amountToTransfer);
            destAccount.Deposit(amountToTransfer + 0.5M);
        }

        
    }



    public interface IAccount
    {
        void Withdraw(decimal v);
        void Deposit(decimal v);
    }

    public class Account : IAccount
    {
        public void Withdraw(decimal amount)
        {
        }

        public void Deposit(decimal amount)
        {
        }
    }
}