Unit test protected method in C# using Moq

前端 未结 3 1888
闹比i
闹比i 2021-02-18 14:48

It came to my attention lately that you can unit test abstract base classes using Moq rather than creating a dummy class in test that implements the abstract base class. See How

相关标签:
3条回答
  • 2021-02-18 15:21

    For starters, there's no point in unit testing an abstract method. There's no implementation! You may want to unit test an impure abstract class, verifying that the abstract method was called:

    [Test]
    public void Do_WhenCalled_CallsMyAbstractMethod()
    {
        var sutMock = new Mock<MyAbstractClass>() { CallBase = true };
        sutMock.Object.Do();
        sutMock.Verify(x => x.MyAbstractMethod());
    }
    
    public abstract class MyAbstractClass
    {
        public void Do()
        {
            MyAbstractMethod();
        }
    
        public abstract void MyAbstractMethod();
    }
    

    Note that I set CallBase to turn this into a partial mock, in case Do was virtual. Otherwise Moq would have replaced the implementation of the Do method.

    Using Protected() you could verify that a protected method was called in a similar manner.

    When you create a mock with Moq or another library, the whole point is overriding implementation. Testing a protected method involves exposing existing implementation. That's not what Moq is designed to do. Protected() just gives you access (presumably through reflection, since it's string-based) to override protected members.

    Either write a test descendant class with a method that calls your protected method, or use reflection in the unit test to call the protected method.

    Or, better yet, don't test protected methods directly.

    0 讨论(0)
  • 2021-02-18 15:29

    You've already touched upon the "test the public API, not private" thought process, and you've also already mentioned the technique of inheriting from the class and then testing its protected members that way. Both of these are valid approaches.

    Beneath it all, the simple truth is that you consider this implementation detail (as that's what a private or protected member is) important enough to test directly rather than indirectly via the public API that would use it. If it is this important, perhaps it's important enough to promote to its own class. (After all, if it's so important, perhaps it is a responsibility that MyAbstractClass should not have.) The instance of the class would be protected inside MyAbstractClass, so only the base and derived types would have access to the instance, but the class itself would be fully testable otherwise and usable elsewhere if that became a need.

    abstract class MyAbstractClass 
    {
         protected ImportantMethodDoer doer;
    }
    
    class ImportantMethodDoer
    {
         public void Do() { }
    }
    

    Otherwise, you're left* to the approaches you've already identified.


    *Moq may or may not provide some mechanism for getting at private or protected members, I cannot say, as I do not use that particular tool. My answer is more from an architectural standpoint.

    0 讨论(0)
  • 2021-02-18 15:36

    Another way in Moq to call protected member is the following template:

    1. In your class, with protected member mark your function as virtual. For example:

      public class ClassProtected
          {
              public string CallingFunction(Customer customer)
              {
                  var firstName = ProtectedFunction(customer.FirstName);
                  var lastName = ProtectedFunction(customer.LastName);
      
                  return string.Format("{0}, {1}", lastName, firstName);
              }
      
              protected virtual string ProtectedFunction(string value)
              {
                  return value.Replace("SAP", string.Empty);
              }
          }
      

    Then in your unit test add reference to

     using Moq.Protected;
    

    and in your unit test you can write the following:

        [TestFixture]
        public class TestClassProttected
        {
            [Test]
            public void all_bad_words_should_be_scrubbed()
            {
                //Arrange
                var mockCustomerNameFormatter = new Mock<ClassProtected>();
    
                mockCustomerNameFormatter.Protected()
                    .Setup<string>("ProtectedFunction", ItExpr.IsAny<string>())
                    .Returns("here can be any value")
                    .Verifiable(); // you should call this function in any case. Without calling next Verify will not give you any benefit at all
    
                //Act
                mockCustomerNameFormatter.Object.CallingFunction(new Customer());
    
                //Assert
                mockCustomerNameFormatter.Verify();
            }
        }
    

    Take note of ItExpr. It should be used instead of It. Another gotcha awaits you at Verifiable. I don't know why, but without calling to Verifiable Verify will not be called.

    0 讨论(0)
提交回复
热议问题