如何模拟 Excel VSTO 插件中的行?

How to mock rows in a Excel VSTO plugin?

我正在尝试在新 Range 的行中放置一个模拟的 Range(其中包含具有值的单元格)。但是当我尝试从 Range 访问特定元素时,抛出异常。

我什么都试过了,有谁知道我做错了什么吗?

异常

Message: Test method xxx.MockUtilsTest.MockRowsTest threw exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot apply indexing with [] to an expression of type 'Castle.Proxies.RangeProxy'

测试

[TestMethod]
public void MockRowsTest()
{
    var row1 = MockUtils.MockCells("test_row_1", "test_row_1");
    var row2 = MockUtils.MockCells("test_row_2", "test_row_2");
    var range = MockUtils.MockRows(row1, row2);

    Assert.IsNotNull(range);
    Assert.AreEqual(2, range.Count);
    Assert.IsNotNull(range.Rows);
    Assert.AreEqual(2, range.Rows.Count);
    Assert.AreSame(row1, range.Rows[1].Cells[1]); // exception is thrown here
    Assert.AreSame(row2, range.Rows[2].Cells[1]);
    Assert.AreEqual("test_row_1", range.Rows[1].Cells[1].Value2);
    Assert.AreEqual("test_row_2", range.Rows[2].Cells[1].Value2);
}

MockUtils

public static Range MockCellValue2(Object value)
{
    var cell = new Moq.Mock<Range>();
    cell.Setup(c => c.Value2).Returns(value);

    return cell.Object;
}

public static Range MockCells(params Object[] values)
{
    var cells = new Moq.Mock<Range>();
    for (int i = 0; i < values.Length; i++)
    {
        var cell = MockCellValue2(values[i]);
        cells.SetupGet(c => c[i + 1, Moq.It.IsAny<Object>()]).Returns(cell);
    }

    var row = new Moq.Mock<Range>();
    row.SetupGet(r => r.Cells).Returns(cells.Object);
    row.SetupGet(r => r.Count).Returns(values.Length);

    return row.Object;
}

public static Range MockRows(params Range[] rows)
{
    var mergedRows = MergeRanges(rows);
    var range = new Moq.Mock<Range>();
    range.SetupGet(r => r.Count).Returns(rows.Length);
    range.SetupGet(r => r.Rows).Returns(() => mergedRows);
    range.Setup(r => r.GetEnumerator()).Returns(rows.GetEnumerator());

    return range.Object;
}

public static Range MergeRanges(params Range[] ranges)
{
    var range = new Moq.Mock<Range>();
    for (int i = 0; i < ranges.Length; i++)
    {
        range.SetupGet(r => r[i + 1, Moq.It.IsAny<Object>()]).Returns(ranges[i]);
    }

    range.SetupGet(r => r.Count).Returns(ranges.Length);
    range.Setup(r => r.GetEnumerator()).Returns(ranges.GetEnumerator());

    return range.Object;
}

Range return 的索引器是一个动态对象,这就是问题的根源。

Moq 使用 Castle Dynamic proxy 生成假对象,Castle.Proxies.RangeProxy 是在您的情况下生成的 class。由于此对象不是 COM 对象,因此正在调用 C# 运行时绑定器的处理。 Runtime binder 解析类型并寻找索引器方法,但他解析失败,因为生成的 class 没有它。

解决您的问题的最简单方法是 return 索引器结果为严格的 Range 局部变量:

那么你的测试将失败,因为 range.Rows[1] 等于 row1...

因此将您的测试代码更改为:

[TestMethod]
public void MockRowsTest()
{
    var row1 = MockUtils.MockCells("test_row_1", "test_row_1");
    var row2 = MockUtils.MockCells("test_row_2", "test_row_2");
    var range = MockUtils.MockRows(row1, row2);

    Assert.IsNotNull(range);
    Assert.AreEqual(2, range.Count);
    Assert.IsNotNull(range.Rows);
    Assert.AreEqual(2, range.Rows.Count);
    Range x = range.Rows[1];
    Range y = range.Rows[2];
    var xCell = x.Cells[1];
    var yCell = y.Cells[1];
    Assert.AreSame(row1, x); 
    Assert.AreSame(row2, y);
    Assert.AreEqual("test_row_1", xCell.Value2);
    Assert.AreEqual("test_row_2", yCell.Value2);
}

以上UT将通过测试。 IMO 你应该中断对 "atomic OPS(multi line) and methods" 的聚合调用,不是因为它会通过测试,因为它会使你的代码成为调试友好的代码。(我称之为 "the 11TH rule" 你的代码将被至少阅读的地方从它被编写的时候起又重复了 10 次...所以让编译器删除可传递的局部变量并使您的代码成为调试友好的代码..)。

Here you can read a simple and short explanation with links on how dynamic works in c#.

Here you can read more about Castle Dynamic Proxy.

顺便说一句;你也可以这样做:

Range x = range.Rows[1].Cells;
var str = x[1].Value2;

接收值