c# - 带有 Action /函数参数的接口(interface)的最小起订量设置

标签 c# unit-testing moq

我正在尝试模拟对服务器的调用并验证测试代码是否调用了正确的方法。代码结构如下:

public interface IServerAdapter
{
    void CallServer(Action<IServerExposingToClient> methodToCall, out bool serverCalled);
    object CallServer(Func<IServerExposingToClient, object> methodToCall, out bool serverCalled);
}

public interface IServerExposingToClient
{
    Resource GetResource(string id);
    void AddResource(Resource resource);
}

我正在测试的代码访问 IServerAdapter 的实现并调用服务器上的 IServerExposingToClient 方法。这样做的原因是我们不想在类中的 IServerExposingToClient 上实现每个方法(有很多方法)。由于随后会在代理上调用它,因此它在我们的代码中运行良好。

在服务器上调用方法是这样完成的:

_mainViewModel.ServerAdapter.CallServer(m => m.AddResource(resource), out serverCalled);

现在的问题是测试和模拟。我需要断言该方法 (AddResource) 已在服务器上调用。 out serverCalled(问题 1)是为了确保调用已到达服务器,以便逻辑按照调用者应有的方式流动。

当我使用以下 Moq 设置时,我可以断言 some 方法已在服务器上调用:

Mock<IServerAdapter> serverAdapterMock = new Mock<IServerAdapter>();
bool serverCalled;
bool someMethodCalled = false;
serverAdapterMock.Setup(c => c.CallServer(It.IsAny<Action<IServerExposingToClient>>(), out serverCalled)).Callback(() => someMethodCalled = true);
// assign serverAdapterMock.Object to some property my code will use
// test code
Assert.IsTrue(someMethodCalled, "Should have called method on server.");

正如测试所说,我可以断言某些方法已在服务器上调用。问题是我不知道哪个 方法被调用了。在某些方法中,我要测试的逻辑可能会根据条件采取不同的路径,并且对于某些场景,多个路径可能会导致服务器调用。因此,我需要知道调用了哪个方法,以便我可以在测试中获得正确的断言(问题 2)。

需要设置变量 serverCalled 以匹配 IServerAdapter mock 上方法调用的签名。我也非常希望能够在某个回调或任何可以让我正在测试的代码的逻辑按照应有的方式流动的东西中设置该变量(问题 1)。由于现在的工作方式,模拟设置不会设置 serverCalled。

主要问题是不知道针对服务器调用了哪个方法。我尝试使用 Match 检查委托(delegate)方法的名称,但没有 sigar。

serverAdapterMock.Setup(c => c.CallServer(Match.Create<Action<IServerExposingToClient>>( x => IsAddResource(x)), out serverCalled)).Callback(() => someMethodCalled = true);

当调用 IsAddResource 时,该函数不引用 AddResource,仅引用它的创建位置和调用时使用的参数。

有谁知道如何检查调用了哪个方法?

对于 out serverCalled 的另一个问题,您可以争辩说 void 方法可以改为 bool,而另一个方法可以如果我们没有与服务器的连接,则返回 null(最后一个参数是不明确的,因为 null 可能意味着对象不存在,或者服务器不可用)。

我非常希望得到解决这两个问题的建议。

提前致谢

最佳答案

问题一:

正如您所指出的,这里的回调是理想的选择。不幸的是,因为您的方法签名有一个 out 参数,它目前无法被 Moq 拦截。有一个 post in the Moq forums这与您的担忧相似。没有迹象表明它会得到解决。

如果您愿意更改您的方法签名,请考虑删除 out 参数——无论如何,它们有点难闻。如果您只是在服务器不可用时抛出异常,事情就会简单得多。从那里出去 (双关语意) 将打开您的回调以处理您的第二个问题。

问题二:

考虑将您的方法签名更改为 Expression>。我意识到那里有很多尖括号,但是 Expression 在语法上等同于 Action (您不必更改调用代码)并且允许您的 Callback 遍历代码表达式树并获取您的名称调用方法。

您可以使用类似的方法获取方法的名称:

public string GetMethodName(Expression<Action<IServerExposingToClient>> exp)
{
    return ((ExpressionMethodCall)exp.Body).Method.Name;
}

您现在有足够的武器库来进行您的模拟。您可以编写记录方法调用名称的回调,也可以编写匹配器来帮助定义调用方法时的行为。

例如:

[Test]
public void WhenTheViewModelIsLoaded_TheSystem_ShouldRecordOneNewResource()
{
   // arrange
   var serverAdapterMock = new Mock<IServerAdapter>();
   var subject = new SubjectUnderTest( serverAdapterMock.Object );

   // act
   subject.Initialize();

   // assert
   serverAdapterMock.Verify( c => c.CallServer( IsAddResource() ), Times.Exactly(1));
}

static Expression<Action<IServerExposedToClient>> IsAddResource()
{
    return Match.Create<Expression<Action<IServerExposedToClient>>>(
              exp => GetMethodName(exp) == "AddResource");
}

关于c# - 带有 Action /函数参数的接口(interface)的最小起订量设置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5109191/

相关文章:

c#检测何时有太多对象

C# 将字符串显式转换为枚举

c# - Microsoft.ApplicationInsights 缺失

unit-testing - Electron 项目上的docker volume文件夹的Ava和权限

javascript - QUnit 不会运行测试

c# - Moq 到 FakeItEasy 的迁移 - 如何迁移Verify()?

unit-testing - 无法将 ReactiveUI 添加到 NUnit 测试项目

c# - 使用 System.Text.Json.Serialization 序列化 const

unit-testing - 带有 "declare var"的测试组件

c# - 如何使用 CancellationToken 作为参数传递来模拟方法?