【问题标题】:Using Moq to mock only some methods使用 Moq 仅模拟一些方法
【发布时间】:2021-04-01 22:50:49
【问题描述】:

我有以下方法:

public CustomObect MyMethod()
{
    var lUser = GetCurrentUser();
    if (lUser.HaveAccess)
    {
        //One behavior
    }
    else 
    {
        //Other behavior
    }

    //return CustomObject
}

我想模拟IMyInterface.GetCurrentUser,以便在调用MyMethod 时可以访问其中一个代码路径来检查它。起订量如何做到这一点?

我正在做以下事情:

var moq = new Mock<IMyInterface>();
moq.Setup(x => x.GetCurrentUser()).Returns(lUnauthorizedUser);

//act
var lResult = moq.Object.MyMethod();

但由于某种原因,lResult 始终是null,当我尝试在调试中进入MyMethod 时,我总是跳到下一条语句。

【问题讨论】:

  • 你在哪里初始化了lUnauthorizedUser?我想你会想要类似moq.Setup(x =&gt; x.GetCurrentUser()).Returns(new User() { HaveAccess = false });
  • Tyler,肯定是我m setting it in the above code, just didnt 粘贴它以保持代码简短。

标签: moq


【解决方案1】:

这称为部分模拟,我知道在 Moq 中执行此操作的方式需要模拟类而不是接口,然后将模拟对象上的“Callbase”属性设置为“true”。

这需要将您正在测试的类的所有方法和属性设为虚拟。假设这不是问题,您可以编写如下测试:

var mock = new Mock<YourTestClass>();
mock.CallBase = true;
mock.Setup(x => x.GetCurrentUser()).Returns(lUnauthorizedUser);
mockedTest.Object.MyMethod();

【讨论】:

  • 太棒了!非常感谢,t think its 没那么简单。
  • 我见过类似的答案,但他们忘记将CallBase 设置为true,非常重要的部分,谢谢!
  • 我听说修改您的应用程序代码只是为了测试是一种代码味道。在这种情况下,您如何看待这种情况?就我而言,现在,我只是在测试一个 ASP.Net REST 控制器,所以我看不到任何可预见的理由来对控制器进行子类化,但添加“虚拟”似乎也不是问题我的方法?
  • @RuneStar 改造测试是一件棘手的事情 - 如果您修改代码以使其可测试,那么它就没有测试,因此您无法防止回归它。将东西标记为虚拟是非常安全的。如果你真的想要你可以将你的类包装在一个接口中(无论如何你应该在 TDD 中这样做),然后设置你想要模拟的方法调用来调用真正的方法。在我看来有点矫枉过正,但在某些情况下值得在工具箱中使用(例如,您只想让一个方法调用成为真正的实现,其余的被模拟)。
  • 这对我有用。我做了我的protected 方法virtual,但我不需要mock.CallBase = true; 我更喜欢Charles W 的回答。
【解决方案2】:

扩展至lee's answer

您不需要将类上的所有方法和属性都设为虚拟,只需要将您希望模拟的那些方法和属性设为虚拟即可。

另外,应该注意的是,您应该模拟类的具体实现。

var mock = new Mock<YourTestClass>(); // vs. var mock = new Mock<IYourTestInterface>();

如果您的类没有默认构造函数,您还需要指定参数以通过以下方式传递给它:

var mock = new Mock<YourTestClass>(x, y, z);
// or
var mock = new Mock<YourTestClass>(MockBehavior.Default, x, y, z);

x, y, z 分别是构造函数的第一个、第二个和第三个参数。

最后,如果您要模拟的方法受到保护,则需要包含 Moq.Protected

using Moq.Protected;

TReturnType returnValue = default(TReturnType);

mock.Protected()
    .Setup<TReturnType>("YourMockMethodName", It.IsAny<int>()) // methodname followed by arguments
    .Returns(returnValue);

【讨论】:

  • 值得注意的是,您可以使用nameof(YourTestClass.YourMockMethodName) 代替“YourMockMethodName”。因此,当您重构方法名称时,测试仍然有效。
【解决方案3】:

我也遇到过类似的情况。我发现下面的代码让我可以更灵活地使用接口的某些特定实现中的模拟方法和实际方法:

var mock = new Mock<ITestClass>(); // Create Mock of interface

// Create instance of ITestClass implementation you want to use
var inst = new ActualTestClass();

// Setup to call method of an actual instance
// if method returns void use mock.Setup(...).Callback(...)
mock.Setup(m => m.SomeMethod(It.IsAny<int>())
    .Returns((int x) => inst.SomeMethod(x));

现在您可以使用实际方法,也可以使用 Verify 之类的东西来查看它被调用了多少次。

【讨论】:

  • 这是一个非常有趣的解决方法,我们可以在不使用虚拟类方法的情况下实现同样的效果。
  • 您是否知道一种为实例的所有方法/属性设置此项的方法,例如自动设置,而不是逐个设置?
  • 这正是我的做法,我想模​​拟的大多数方法都是纯函数,我想调用其实际版本。我像这样静态地调用它们,而不是写出本质上只是重复它们所做的返回。
【解决方案4】:

由于这是搜索时的最佳结果,扩展 lee 的答案,您可以通过使用 As&lt;T&gt;() 方法分配接口来避免使用虚拟方法。

var mock = new Mock<YourTestClass>().As<IYourTestClass>();
mock.CallBase = true;

var realResult = mock.Object.YourMethod();

mock.Setup(c => c.YourMethod()).Returns("FAKE");

var fakeResult = mock.Object.YourMethod();

但是请注意,如果您的类在内部使用您以这种方式模拟的方法,它仍然会调用真实方法,因为它不知道模拟类型并且从this 调用。这会降低这种方法的可用性,并且可能会让您(就像我一样)理解为什么关于这种模式的文档很少。

【讨论】:

    猜你喜欢
    • 2010-11-12
    • 2011-01-18
    • 2022-10-24
    • 2011-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-23
    相关资源
    最近更新 更多