首页 > 解决方案 > 最小起订量在列出呼叫时说 0 个呼叫?

问题描述

我有一个使用 Moq 模拟接口和验证调用的单元测试。这是测试代码:

[Fact]
public void NewBlank_InvokesManagerAdd()
{
    // ReSharper disable once AssignNullToNotNullAttribute
    var newPath = Path.Combine(_testSaveDirectory, "InvokeBlank.txt");

    _dbManagerMock.Setup(manager => manager.KeynoteDBs).Returns(new ObservableCollection<KeynoteDBVM>());
    _dialogMock.Setup(dialog => dialog.GetSaveFileDialogResult(It.IsAny<SaveFileDialogData>()))
               .Returns(newPath);

    _commands.CmdNewBlank.Execute(null);

    _dbManagerMock
        .Verify(manager => manager.AddDB(It.IsAny<KeynoteDB>(), It.IsAny<int>()),
                Times.Once);
}

但是,当我运行它时,我得到了这个测试失败:

Moq.MockException

Expected invocation on the mock once, but was 0 times: manager => manager.AddDB(It.IsAny<KeynoteDB>(), It.IsAny<int>())

Performed invocations:

   Mock<IDBManager:1> (manager):

      IDBManager.KeynoteDBs
      IDBManager.IsLoading = True
      IDBManager.ActiveDB
      IDBManager.AddDB(KeynoteDB, -1)
      IDBManager.ActiveDB
      IDBManager.IsLoading = False

   at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
   at Moq.Mock`1.Verify[TResult](Expression`1 expression, Func`1 times)
   at KMCore_Tests.AppCommandsTests.NewBlank_InvokesManagerAdd() in *my path*\AppCommandsTests.cs:line 140

它不是在中间的调用列表中列出该调用 1 次吗?怎么说它有 0 次调用?我错过了什么?我觉得我一定错过了一些愚蠢的东西,但我看不到它......

编辑

好的,结果证明这是一个竞争条件问题,因为命令是异步的。命令执行调用了一个 async void 方法,并且它必须在实际调用之前就已经达到了断言(或者至少这是我能想到的全部)。我在调用执行之后和断言之前放入了一个 await Task.Delay(500) ,它现在正在通过。

有没有更好的方法来测试这种情况?这些命令本质上是按钮处理程序,因此根据我的理解,我认为 async void 在这里是正确的,但这意味着我不能在单元测试中等待它...

标签: c#unit-testingmoq

解决方案


你应该避免使用 async void

async void意味着火和忘记。在您的情况下,您似乎不希望这样做,因为您需要等到异步方法完成。

如果您无法更改方法签名,则可以将async void方法体移动到Task返回方法。

例如,改变

public async void MyAsyncVoidMethod()
{
    await Task.Delay(500);

    MethodToBeCalled();
}

// Wait this task in unit test
public Task MyAsyncTask { get; private set; }

public async void MyAsyncVoidMethod()
{
    MyAsyncTask = MyAsyncTaskMethod();
    await MyAsyncTask;
}

public async Task MyAsyncTaskMethod()
{
    await Task.Delay(500);

    MethodToBeCalled();
}

或者,如果在命令完成后触发了任何事件,您还可以挂钩其中一个事件以在您的测试中保持通知。


推荐阅读