我正在尝试模拟对服务器的调用,并验证测试的代码是否调用了正确的方法。代码结构如下:
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可能意味着对象不存在,或者服务器不可用)。
我非常想解决这两个问题的建议。
提前致谢
答案 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");
}