具有动作/函数参数的接口的Moq设置

时间:2011-02-24 19:00:26

标签: 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设置时,我可以声明已经在服务器上调用了某些方法:

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来检查委托方法的名称,但没有使用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如果我们不知道t与服务器有连接(最后一个参数不明确,因为null可能意味着对象不存在,或者服务器不可用)。

我非常想解决这两个问题的建议。

提前致谢

1 个答案:

答案 0 :(得分:6)

问题一:

正如您所指出的,这里的回调将是理想的。不幸的是,因为你的方法签名有一个 out 参数,所以它目前不能被Moq拦截。有post in the Moq forums与您的关注点类似。没有任何迹象表明它会被解决。

如果您愿意更改方法签名,请考虑删除 out 参数 - 无论如何它们都有点气味。如果您在服务器不可用时抛出异常,事情就会简单得多。获得(双关语) out 会打开你的Callback来处理你的第二个问题。

问题二:

考虑将您的方法签名更改为Expression&lt; Action&lt; IServerExposingToClient&gt;&gt;。我意识到那里有很多尖括号,但是Expression在语法上等同于Action&lt; T&gt; (您不必更改调用代码)并允许您的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");
}