C# 使用 Moq 验证调用的顺序是否正确
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10602264/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Moq to verify calls are made in the correct order
提问by g t
I need to test the following method:
我需要测试以下方法:
CreateOutput(IWriter writer)
{
writer.Write(type);
writer.Write(id);
writer.Write(sender);
// many more Write()s...
}
I've created a Moq'd IWriterand I want to ensure that the Write()methods are called in the right order.
我已经创建了一个 Moq'd IWriter,我想确保Write()以正确的顺序调用这些方法。
I have the following test code:
我有以下测试代码:
var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));
However, the second call to Write()in CreateOutput()(to write the idvalue) throws a MockExceptionwith the message "IWriter.Write() invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.".
但是,第二次调用Write()in CreateOutput()(写入id值)会抛出MockException带有消息“ IWriter.Write() 调用失败,模拟行为严格。模拟上的所有调用都必须具有相应的设置。”。
I'm also finding it hard to find any definitive, up-to-date documentation/examples of Moq sequences.
我还发现很难找到任何明确的、最新的 Moq 序列文档/示例。
Am I doing something wrong, or can I not set up a sequence using the same method? If not, is there an alternative I can use (preferably using Moq/NUnit)?
我做错了什么,或者我不能使用相同的方法设置序列?如果没有,有没有我可以使用的替代方法(最好使用 Moq/NUnit)?
采纳答案by Sergey Berezovskiy
There is bug when using MockSequence on same mock. It definitely will be fixed in later releases of Moq library (you can also fix it manually by changing Moq.MethodCall.Matchesimplementation).
在同一个 mock 上使用 MockSequence时存在错误。它肯定会在 Moq 库的后续版本中修复(您也可以通过更改Moq.MethodCall.Matches实现手动修复它)。
If you want to use Moq only, then you can verify method call order via callbacks:
如果您只想使用 Moq,那么您可以通过回调验证方法调用顺序:
int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));
回答by John Nicholas
I suspect that expectedId is not what you expect.
我怀疑 expectedId 不是你所期望的。
However i'd probably just write my own implementation of IWriter to verify in this case ... probably a lot easier (and easier to change later).
但是,在这种情况下,我可能只是编写自己的 IWriter 实现来验证……可能会容易得多(并且以后更容易更改)。
Sorry for no Moq advice directly. I love it, but haven't done this in it.
很抱歉没有直接提供起订量建议。我喜欢它,但还没有这样做。
do you maybe need to add .Verify() at the end of each setup? (That really is a guess though i'm afraid).
您可能需要在每次设置结束时添加 .Verify() 吗?(这真的是一个猜测,虽然我很害怕)。
回答by g t
I've managed to get the behaviour I want, but it requires downloading a 3rd-party library from http://dpwhelan.com/blog/software-development/moq-sequences/
我设法得到了我想要的行为,但它需要从http://dpwhelan.com/blog/software-development/moq-sequences/下载一个 3rd-party 库
The sequence can then be tested using the following:
然后可以使用以下方法测试序列:
var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
mockWriter.Setup(x => x.Write(expectedType)).InSequence();
mockWriter.Setup(x => x.Write(expectedId)).InSequence();
mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}
I've added this as an answer partly to help document this solution, but I'm still interested in whether something similar could be achieved using Moq 4.0 alone.
我添加了这个作为答案,部分是为了帮助记录这个解决方案,但我仍然对是否可以单独使用 Moq 4.0 来实现类似的东西感兴趣。
I'm not sure if Moq is still in development, but fixing the problem with the MockSequence, or including the moq-sequences extension in Moq would be good to see.
我不确定 Moq 是否仍在开发中,但解决MockSequence. 或在 Moq 中包含 moq-sequences 扩展的问题会很高兴。
回答by Grzesiek Galezowski
Recently, I put together two features for Moq: VerifyInSequence() and VerifyNotInSequence(). They work even with Loose Mocks. However, these are only available in a moq repository fork:
最近,我为 Moq 组合了两个特性:VerifyInSequence() 和 VerifyNotInSequence()。他们甚至可以使用 Loose Mocks。但是,这些仅在 moq 存储库分支中可用:
https://github.com/grzesiek-galezowski/moq4
https://github.com/grzesiek-galezowski/moq4
and await more comments and testing before deciding on whether they can be included in official moq releaase. However, nothing prevents you from downloading the source as ZIP, building it into a dll and giving it a try. Using these features, the sequence verification you need could be written as such:
并等待更多评论和测试,然后再决定它们是否可以包含在官方最小起订量发布中。但是,没有什么可以阻止您以 ZIP 格式下载源代码,将其构建到 dll 中并尝试一下。使用这些功能,您需要的序列验证可以写成这样:
var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() };
//perform the necessary calls
mockWriter.VerifyInSequence(x => x.Write(expectedType));
mockWriter.VerifyInSequence(x => x.Write(expectedId));
mockWriter.VerifyInSequence(x => x.Write(expectedSender));
(note that you can use two other sequences, depending on your needs. Loose sequence will allow any calls between the ones you want to verify. StrictSequence will not allow this and StrictAnytimeSequence is like StrictSequence (no method calls between verified calls), but allows the sequence to be preceeded by any number of arbitrary calls.
(请注意,您可以使用其他两个序列,具体取决于您的需要。松散序列将允许您要验证的序列之间进行任何调用。StrictSequence 不允许这样做,而 StrictAnytimeSequence 类似于 StrictSequence(已验证调用之间没有方法调用),但允许任意数量的任意调用之前的序列。
If you decide to give this experimental feature a try, please comment with your thoughts on: https://github.com/Moq/moq4/issues/21
如果您决定尝试此实验性功能,请在以下位置发表您的想法:https: //github.com/Moq/moq4/issues/21
Thanks!
谢谢!
回答by Justin Ryder
I wrote an extension method that will assert based on order of invocation.
我编写了一个扩展方法,它将根据调用顺序进行断言。
public static class MockExtensions
{
public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
{
// All closures have the same instance of sharedCallCount
var sharedCallCount = 0;
for (var i = 0; i < expressions.Length; i++)
{
// Each closure has it's own instance of expectedCallCount
var expectedCallCount = i;
mock.Setup(expressions[i]).Callback(
() =>
{
Assert.AreEqual(expectedCallCount, sharedCallCount);
sharedCallCount++;
});
}
}
}
It works by taking advantage of the way that closures work with respect to scoped variables. Since there is only one declaration for sharedCallCount, all of the closures will have a reference to the same variable. With expectedCallCount, a new instance is instantiated each iteration of the loop (as opposed to simply using i in the closure). This way, each closure has a copy of i scoped only to itself to compare with the sharedCallCount when the expressions are invoked.
它的工作原理是利用闭包对作用域变量的工作方式。因为只有一个 sharedCallCount 声明,所以所有的闭包都将引用同一个变量。使用 expectedCallCount,每次循环迭代都会实例化一个新实例(而不是简单地在闭包中使用 i)。这样,每个闭包都有一个 i 的副本,范围仅限于它自己,以便在调用表达式时与 sharedCallCount 进行比较。
Here's a small unit test for the extension. Note that this method is called in your setup section, not your assertion section.
这是扩展的一个小单元测试。请注意,此方法是在您的设置部分而不是您的断言部分中调用的。
[TestFixture]
public class MockExtensionsTest
{
[TestCase]
{
// Setup
var mock = new Mock<IAmAnInterface>();
mock.ExpectsInOrder(
x => x.MyMethod("1"),
x => x.MyMethod("2"));
// Fake the object being called in order
mock.Object.MyMethod("1");
mock.Object.MyMethod("2");
}
[TestCase]
{
// Setup
var mock = new Mock<IAmAnInterface>();
mock.ExpectsInOrder(
x => x.MyMethod("1"),
x => x.MyMethod("2"));
// Fake the object being called out of order
Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
}
}
public interface IAmAnInterface
{
void MyMethod(string param);
}
回答by Ufuk Hac?o?ullar?
回答by Karlas
My scenario was methods without parameters:
我的场景是没有参数的方法:
public interface IWriter
{
void WriteA ();
void WriteB ();
void WriteC ();
}
So I used Invocationsproperty on the Mockto compare what was called:
所以我使用Invocations属性Mock来比较所谓的:
var writer = new Mock<IWriter> ();
new SUT (writer.Object).Run ();
Assert.Equal (
writer.Invocations.Select (invocation => invocation.Method.Name),
new[]
{
nameof (IWriter.WriteB),
nameof (IWriter.WriteA),
nameof (IWriter.WriteC),
});
You could also append the invocation.Argumentsto check method calls with parameters.
您还可以将 附加invocation.Arguments到带有参数的检查方法调用。
Also the failure message is more clear than just expected 1 but was 5:
失败信息也比仅仅更清楚expected 1 but was 5:
expected
["WriteB", "WriteA", "WriteC"]
but was
["WriteA", "WriteB"]

