使用 Moq 在 C# 中进行单元测试受保护的方法

问题描述 投票:0回答:4

最近我注意到,您可以使用 Moq 对抽象基类进行单元测试,而不是在测试中创建实现抽象基类的虚拟类。请参阅如何使用起订量来测试抽象类中的具体方法?你可以这样做:

public abstract class MyAbstractClass 
{
    public virtual void MyMethod()
    {
        // ...    
    }
}

[Test]
public void MyMethodTest()
{
    // Arrange            
    Mock<MyAbstractClass> mock = new Mock<MyAbstractClass>() { CallBase = true };

    // Act
    mock.Object.MyMethod();

    // Assert
    // ... 
}

现在我想知道是否有类似的技术可以让我测试受保护的成员而无需创建包装类。 IE。你如何测试这个方法:

public class MyClassWithProtectedMethod
{
    protected void MyProtectedMethod()
    {

    }
}

我知道 Moq.Protected 命名空间,但据我所知,它只允许您设置期望,例如

mock.Protected().Setup("MyProtectedMethod").Verifiable();

我也知道这里明显的答案是“不要测试受保护的方法,只测试公共方法”,但这是另一场争论了!我只是想知道使用起订量是否可以实现这一点。

更新:下面是我通常如何测试的:

public class MyClassWithProtectedMethodTester : MyClassWithProtectedMethod
{
    public void MyProtectedMethod()
    {
        base.MyProtectedMethod();
    }
}

提前致谢。

c# moq
4个回答
39
投票

Moq 中调用受保护成员的另一种方法是以下模板:

  1. 在您的班级中,使用受保护的成员将您的函数标记为虚拟。 例如:

    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);
            }
        }
    

然后在您的单元测试中添加对

的引用
 using Moq.Protected;

并且在单元测试中您可以编写以下内容:

    [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();
        }
    }

记下 ItExpr。应该使用它来代替 It。另一个陷阱在 Verabilable 等待着您。我不知道为什么,但是如果不调用可验证验证将不会被调用。


12
投票

对于初学者来说,对抽象方法进行单元测试是没有意义的。没有实施!您可能想要对不纯的抽象类进行单元测试,验证抽象方法是否被调用:

[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();
}

请注意,我设置 CallBase 将其转换为部分模拟,以防 Do 是虚拟的。否则 Moq 将取代 Do 方法的实现。

使用 Protected() 您可以验证是否以类似的方式调用了受保护的方法。

当您使用 Moq 或其他库创建模拟时,重点是覆盖实现。测试受保护的方法涉及公开现有的实现。这不是 Moq 的设计目的。 Protected() 只是让您可以访问(可能是通过反射,因为它是基于字符串的)来覆盖受保护的成员。

要么编写一个带有调用受保护方法的方法的测试后代类,要么在单元测试中使用反射来调用受保护方法。

或者,更好的是,不要直接测试受保护的方法。


7
投票

您已经触及了“测试公共 API,而不是私有 API”的思维过程,并且还提到了从类继承然后以这种方式测试其受保护成员的技术。这两种方法都是有效的。

在这一切之下,一个简单的事实是,您认为这个实现细节(因为这就是私有或受保护的成员)足够重要,可以直接进行测试,而不是通过使用它的公共 API 进行间接测试。如果它这么重要,也许它重要到足以晋升到它的自己的类别。 (毕竟,如果它如此重要,也许这是

MyAbstractClass
不应该承担的责任。)类的实例将在
MyAbstractClass
内受到保护,因此只有基类和派生类型才能访问该实例,但类本身将是完全可测试的,并且如果需要的话也可以在其他地方使用。

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}

class ImportantMethodDoer
{
     public void Do() { }
}

否则,您就只能*使用已经确定的方法。


*Moq 可能会或可能不会提供某种获取私有或受保护成员的机制,我不能说,因为我不使用该特定工具。我的回答更多是从架构的角度来看的。


0
投票

这是一个特殊且简单的情况,因为方法有不同的名称,当我们需要测试对重写方法的某些调用时就会出现问题。

请注意:

  • 我避免调用显式的base.Method1(),只调用Method1()。
  • 我已经向调用添加了参数,以便也测试参数调用。

这里是代码:

 using Moq;
 using Moq.Protected;

 public abstract class MyBaseClass
 {
     protected virtual void Method1(string param)
     {
     }
 }

 public class MyDerivedClass : MyBaseClass
 {
     public void Method2(string param)
     {
         Method1($"called=>{param}");
     }
 }


 [TestMethod]
 public void Method2_MustCallMethod1()
 {
     var mock = new Mock<MyDerivedClass>();
     var testClass = mock.Object;

     var param = "someRandomValue";
     var callParam = $"called=>{param}";

     mock.Protected()
         .Setup("Method1", ItExpr.IsAny<string>());

     testClass.Method2(param);

     mock.Protected()
         .Verify("Method1", Times.Once(), callParam);

 } 
© www.soinside.com 2019 - 2024. All rights reserved.