如何使用Moq模拟包含内部抽象方法的抽象类?

6

我有一个类A,我正在对其中的一个方法进行单元测试,该方法以类B作为参数。我试图模拟抽象类B。该类类似于下面的代码:

public abstract class B
{
    internal abstract void DoSomething();
}

我的单元测试看起来像这样。

[TestMethod]
public void ClassA_Add_TestSomething()
{
    var classA = new A();
    var mock   = new Mock<B>();

    classA.Add(mock.Object);

    // Assertion
}

我收到了以下异常:
测试方法TestSomething引发了异常:参数异常:模拟类型必须是接口、抽象或非密封类。 --->类型加载异常:程序集'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' 中的类型 'Castle.Proxies.BProxy' 的方法“DoSomething”没有实现。
我可以通过将方法设置为虚拟的而不是抽象的来解决这个问题,但这并不是我想要在 API 设计中实现的。我还尝试通过mock.Setup(m => m.DoSomething())为其提供实现,但没有成功。这是否可能使用Moq完成,或者我将不得不创建一个从抽象类B派生的具体测试类?我想避免创建具体类型,这有点违背mocking或stubbing框架使用的一些目的,或者我被误导了吗?
编辑 我已经发现如果我使方法公开的抽象的,那么这个问题就不会出现。所以我想问的真正问题是,当使用InternalsVisibleTo和Moq时,是否可以对内部方法进行处理?

为什么你不能使用 mock.Setup? - brz
无论我是否使用Setup,我都会收到异常。在我的单元测试中,我不关心B.DoSomething是否有实现,尽管我理解为什么它需要一个实现。可能我没有正确地使用Setup,但我尝试过的是相当基本的mock.Setup(m=>m.DoSomething()).Callback(() => {}); - David Anderson
4
仔细考虑为什么公共类中有一个“internal abstract”方法。其他人无法实现抽象方法,因为它对他们不可见。也许这正是您想要的,但我建议将构造函数也设为 internal,以确保没有人会尝试去实现抽象类。 - Mike Zboray
+1 因为我同意。这是组件开发(不是 UI),其中有一些内部通信,以使组件正确地协同工作(类似于 TabControl 和其子选项卡)。除了在我的情况下是条形码处理。我正在积极重新思考设计,也许可以找到更好的方法,但现在即使我有这个当前警告,它实际上还是相当不错的。 - David Anderson
@mikez 你能把你的评论转成答案吗?这样我就可以标记它为已接受的答案了。在我的情况下,解决方案是修改设计,使其更易于测试和简洁。 - David Anderson
@DavidAnderson-DCOM 当然,见下文。 - Mike Zboray
2个回答

6

Moq依赖于Castle Dynamic Proxy来实现其模拟。运行时,Moq会创建一个新的程序集并加载到您的AppDomain中(在异常消息中出现的名称为DynamicProxyGenAssembly2)。

问题在于,这个程序集无法访问您自己代码的内部类和成员,因为它们在声明它们的程序集之外不可见。

一个解决方法是使用InternalsVisibleToAttribute标记您的程序集,并指定动态生成的程序集的名称:

[InternalsVisibleTo("DynamicProxyGenAssembly2")]

请记住,这个解决方案依赖于实现细节,并且可能在未来版本中停止工作。

3
总的来说这很有帮助,但在我的情况下,InternalsVisibleTo已经被适当地应用了。问题在于Moq如何处理抽象类型上的内部方法。最终答案就像MikeZ建议的那样,需要重新考虑设计。 - David Anderson
1
正如@DavidAnderson-DCOM所提到的,这个解决方案在这种情况下不起作用。我可以在自己的测试中确认这一点。 - Ken Sykora
使用[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]using System.Runtime.CompilerServices,然后我就可以模拟内部类了。 - akasoggybunz

3

仔细考虑一下为什么在公共类上有一个internal abstract方法。其他人将无法实现抽象方法,因为它对他们不可见。也许这正是你想要的。有时,在给定各种设计约束条件的情况下,这是一种有效的方法。如果你的库将被其他人使用,我建议至少将构造函数也设置为内部的,这样没有人会认为他们可以实现抽象类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接