2019年1月25日 星期五

[moq] Mock delegate


 C#   Unit test   moq   delegate


Introduction


This article shows how to mock delegate for unit-testing.




Environment


Visual Studio 2017 community
dotnet core 2.2.101
Moq 4.10.1


Implement


We will test the Get(…) function of class, DataAccessService<T> which implements interface, IDataAccessService<T>.  It has a delegate, AuthorizeEvent, which we will mock in this test.

The unit test’s goal:
1.  Test Get(…) function’s accuracy
2.  The delegate is called

Interfaces and Classes

IDataAccessService.cs

public delegate bool Authorize(string caller);

  public interface IDataAccessService<T>
  {
        string ConnectionStr { get; set; }
        event Authorize AuthorizeEvent;
        T Get(string id);
  }



DataAccessService.cs

public class DataAccessService<T> : IDataAccessService<T> where T:new()
    {
        private readonly string _caller = "DataAccessService";
        public string ConnectionStr { get; set; }
        public event Authorize AuthorizeEvent;

        public DataAccessService()
        {

        }
        public T Get(string id)
        {
            //HACK: Implement the logic here to replace the below line
                if (this.AuthorizeEvent(this._caller) == true)
                    return new T();
                else
                    return default(T);
        }
    }


Unit test without mocking the delegate

Lets see a bad idea of not mocking the delegate in the unit test.
That means we have to create a method as a reference for the delegate, and a static flag to make sure the delegate is triggered.


private static bool callbackIsTriggered = false;

[Test]
  public void TestGet()
  {
            IDataAccessService<Birthday> dataAccess = new DataAccessService<Birthday>();

            #region Use the real method
            dataAccess.AuthorizeEvent += MySaveLogAction;
            var rtn = dataAccess.Get(It.IsAny<string>());
            Assert.IsTrue(callbackIsTriggered);
            Assert.IsFalse(rtn == null);
            #endregion
 }

 private static bool MySaveLogAction(string caller)
 {
            Debug.WriteLine($"Called from {caller}");
            callbackIsTriggered = true;
            return true;
 }
    }




Unit test by mocking delegate with moq

We will use moq to mock the delegate, so that we can focus on tesing the IDataAccessService.Get() function.

[Test]
  public void TestGet()
  {
            IDataAccessService<Birthday> dataAccess = new DataAccessService<Birthday>();

            #region Mock the callback
            var mockCallback = new Mock<Authorize>();
            mockCallback.Setup(x => x(It.IsAny<string>())).Returns(true);
            dataAccess.AuthorizeEvent += mockCallback.Object;
            dataAccess.Get(It.IsAny<string>());
            mockCallback.Verify(x => x(It.IsAny<string>()), Times.Once);
Assert.IsFalse(rtn == null);
            #endregion
  }

The above code mocks the delegate: Authorize, and setup the returning value. (If the delegate returns nothing, then ignore .Returns(…)) in this line:

mockCallback.Setup(x => x(It.IsAny<string>())).Returns(true);

And notice we CANNOT directly mock the Invoke method of a delegate like this:
 mockCallback.Setup(x => x.Invoke(It.IsAny<string>())).Returns(true);
Which will cause the error:
System.InvalidCastException : Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.InvocationExpression'.



Reference







沒有留言:

張貼留言