如何使用Moq模拟Microsoft.Office.Interop.Excel.Range?

6

我想模拟 Microsoft.Office.Interop.Excel.Range(和其他 Microsoft.Office.Interop.Excel 接口)来对我的应用程序进行单元测试。我正在使用 Moq 4.0.10827 和 .NET 4 在 C# 中。

在我的单元测试中,我尝试设置模拟的行为如下:

        var range = new Mock<Microsoft.Office.Interop.Excel.Range>();
        range.Setup(r => r.get_Value(1)).Returns("Something");

        var classBeingTested = new MyClass(range.Object);

在进行单元测试的代码中,我使用范围(Range)如下:

    public MyClass(Range range)
    {
        var value = range[1].ToString();
    }

当测试运行时,会产生以下异常:
        Error: Missing method 'instance object [My.Example.Implementation.MyClass] Microsoft.Office.Interop.Excel.Range::get__Default(object,object)' from class 'Castle.Proxies.RangeProxy'.

如何成功实现模拟?我意识到使用像https://dev59.com/OWkx5IYBdhLWcg3wCP6Y#9486226这样的模式来隔离Excel Interop功能是一种潜在的解决方案;但我更喜欢能够从Microsoft.Office.Interop.Excel接口创建模拟。

编辑:有关此问题的更多详细信息

好吧,这很奇怪。我创建了一个项目来测试jimmy_keen的建议。它有效。但是,当我使用相同的建议测试我遇到问题的项目时, 它会抛出以下异常:

Error: Missing method 'instance object [My.Example.Implementation.MyClass] Microsoft.Office.Interop.Excel.Range::get__Default(object,object)' from class 'Castle.Proxies.RangeProxy'.

由于jimmy_keen的建议在其他项目上运行良好,我开始怀疑正在测试代码的项目存在问题。因此,我创建了一个测试解决方案以详细说明问题:http://www7.zippyshare.com/v/70834394/file.html 问题的根源似乎是该项目包含另一个类(未进行单元测试),其基本上具有以下代码:
using Microsoft.Office.Interop.Excel;
namespace Project
{
    public class UnTestedClass
    {
        public UnTestedClass(Worksheet sheet)
        {
            var foo = sheet.Range["Description"].Column;
        }
    }
}

我不明白为什么会出现这个问题,因为我在单元测试中根本没有使用UnTestedClass。我是不是在使用Moq时漏掉了什么,还是说我碰巧遇到了一个bug?


+1 这很奇怪,我猜测这与COM对象有关。一旦我引用了Excel互操作性,我就不能再引用我的Moq库... 奇怪... - jsmith
经过一些工作,我现在正在朝着使用自己的定制类和接口来隔离Microsoft.Office.Interop.Excel功能的方向发展,而不是直接模拟Microsoft.Office.Interop.Excel.*。虽然这个问题的答案确实解决了即时问题,但是最终模拟Microsoft.Office.Interop.Excel.*开始需要太多的设置工作才能使它变得有用。 - Lauri Harpf
1个回答

5

你的代码访问索引器,这就是你需要模拟的内容:

var range = new Mock<Microsoft.Office.Interop.Excel.Range>();
range.Setup(r => r[1, It.IsAny<object>()]).Returns("something");

请注意,Range索引器实际上需要两个参数 - 因此在调用中使用了It.IsAny<object>()限制条件。

谢谢,我测试了一下,它在某些情况下确实有效。然而,我正在处理的单元测试仍然顽固地拒绝工作。请查看我上传到http://www7.zippyshare.com/v/70834394/file.html的测试解决方案。 - Lauri Harpf
2
@LauriHarpf:这很有趣。 显然,这是编译器生成代码的“问题”。在您的项目中,编译器生成的Range类型接口具有额外的属性(即Column)。我不知道这对Castle来说可能会有什么问题...但它是COM。例如,如果您使用ILSpy检查您的程序集,您将看到我的意思。即使类相同,编译器生成的代码也是不同的,这必须是问题所在。请注意,如果您存根range.Setup(r => r.Column).Returns(0);,此测试也将通过。 - k.m
1
太好了,感谢!ILSpy的提示也非常有用;在我的实际项目中,我发现除了“Column”之外还出现了其他额外属性。“Column”、“Columns”、“Row”、“Rows”和“Value”都存在。这种行为可能会使我编写的任何单元测试变得非常脆弱,因为随时可能会出现新的属性并导致某些问题。无论如何,通过使用您的提示并仔细模拟所有额外属性,这个问题已经解决了,所以我把它标记为答案。非常感谢! - Lauri Harpf

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