使用Moq,如何在另一种方法的Callback中更改方法的设置?

时间:2017-08-03 08:21:05

标签: c# unit-testing mocking moq

我试图为我的测试模拟第三方组件。 该组件可以存储一个字节数组,但没有属性,只有两种方法可以访问它。

// simple demo code, _comp is an object of this component
byte[] val = new byte[] { 5 };
// write new value
await _comp.WriteAsync(val);
// read value
byte[] newVal = _comp.ReadAsync();

现在在我的测试中,我想我可以设置WriteAsync方法来更改ReadAsync方法的Setup以返回传入的值。

Mock<IComponent> comp = new Mock<IComponent>;
comp.Setup(x => x.WriteAsync(It.IsAny<byte[]>())).Callback<byte[]>(bytes =>
        {
            comp.Setup(x => x.ReadAsync()).ReturnsAsync(bytes);
        });

但是当我在测试中调试演示代码时,一切运行良好,

comp.Setup(x => x.ReadAsync()).ReturnsAsync(bytes);
使用正确的值调用

,但是当WriteAsync调用返回时,我在行中的演示代码中获得NullReferenceException

await _comp.WriteAsync(val);

我不知道这里的空是什么,_compval不是。 我相信这是Moq框架内部的深层内容,但我没有得到任何有关它的堆栈跟踪信息。 Stacktrace只包含我的演示代码。

我在这里做错了吗? 或者是否无法从其自己的一个回调中更改Mock-object? 我认为,我需要的可以用一个属性完成,但不幸的是该组件没有。

2 个答案:

答案 0 :(得分:1)

将写入的字节存储在本地变量中作为模拟的后备存储。在回调中,您可以设置数组的值,以便在ReadAsync中使用时可用。

WriteAsync也是异步方法,因此需要返回Task以允许异步流。

Mock<IComponent> comp = new Mock<IComponent>();
byte[] bytes = null;
comp.Setup(x => x.WriteAsync(It.IsAny<byte[]>()))
    .Callback<byte[]>(b => bytes = b)
    .Returns(Task.FromResult((object)null));
comp.Setup(x => x.ReadAsync()).ReturnsAsync(bytes);

根据评论进行更新。

在评论中提到WriteAsync返回Task<bool>。虽然返回值的条件基于OP中提供的内容是未知的,但是具有基于所提供的数组的条件的示例可以看起来像这样

.Returns<byte[]>(b => Task.FromResult(b != null && b.Length > 0));

答案 1 :(得分:1)

所以对于偶然发现这个的人来说,我的初始代码是有效的,但是当该方法返回void之外的其他东西时,必须指定这个。 异步方法返回任务或任务&lt;&gt;,因此安装代码应该是例如

Mock<IComponent> comp = new Mock<IComponent>;
comp.Setup(x => x.WriteAsync(It.IsAny<byte[]>())).Callback<byte[]>(bytes =>
    {
        comp.Setup(x => x.ReadAsync()).ReturnsAsync(bytes);
    }).ReturnsAsync(true);