我有一个带有 WCF 服务的典型 Silverlight 应用程序,我正在使用 slsvcutil.exe 生成标准客户端代理以与 Web 服务进行通信。我正在尝试编写单元测试,并且正在尝试使用 Silverlight 单元测试框架和 Moq 来模拟代理并删除服务依赖项以进行测试。
我对 Moq 非常陌生,并且在进行服务调用以模拟异步调用时自动在模拟代理上自动引发各种 Completed 事件时遇到了很多麻烦。
为了使代理“可模拟”,我为生成的代理调用及其完成的事件创建了自己的简单接口(interface):
public interface IServiceProxy
{
void TestAsync(TestRequest request);
void TestAsync(TestRequest request, object userState);
event EventHandler<TestCompletedEventArgs> TestCompleted;
}
我还对生成的代理对象进行了子类化以实现该接口(interface):
public class MyServiceProxy : GeneratedServiceClient, IServiceProxy, ICommunicationObject
{
// ... overloaded proxy constructors
}
在查看了 Moq 文档之后,这就是我试图设置模拟以期望 TestAsync() 调用并立即引发 TestCompleted 事件,结果在 EventArgs 中:
[TestMethod]
public void Test_Returns_Expected()
{
var mockProxy = new Mock<IServiceProxy>();
var result = new TestResponse() { Value = true };
this.mockProxy.Setup(
p => p.TestAsync(It.IsAny<TestRequest>()))
.Raises(p => p.TestCompleted += null, new TestCompletedEventArgs(new object[] { result }, null, false, null));
// rest of the test to actually use the mock and assert things
}
一切都很好,但是当我尝试使用模拟运行任何类型的测试并设置断点时,当我调用 TestAsync() 时,永远不会引发 TestCompleted 事件。
关于在 Silverlight 中模拟这些类型的异步服务代理,我是否有任何明显的遗漏或任何更好的想法?
谢谢!
编辑:
更清楚地说,我实际上要测试的是我创建的一个辅助类,它采用
IServiceProxy
的实例。并通过接受 Action<TResponse, Exception>
为我的 ViewModel 提供更简洁的服务接口(interface)。回调参数,而不是处理我的 ViewModel 中的回调事件。我知道我如何也可以模拟它以直接测试我的 ViewModel,但我认为先测试帮助程序类本身会很好。这是我正在谈论的一个例子:
public class HelperClient : IServiceHelper
{
private IServiceProxy proxy;
public HelperClient(IServiceProxy proxy)
{
this.proxy = proxy;
// register to handle all async callback events
this.proxy.TestCompleted += new EventHandler<TestCompletedEventArgs>(TestCompleted);
}
public void Test(TestRequest request, Action<TestResponse, Exception> response)
{
this.proxy.TestAsync(request, response);
}
private void TestCompleted(object sender, TestCompletedEventArgs e)
{
var response = e.UserState as Action<TestResponse, Exception>;
if (response != null)
{
var ex = GetServiceException(e);
if (ex == null)
{
response(e.Result, null);
}
else
{
response(null, ex);
}
}
}
}
因此,在我的测试中,我真正在做的是模拟 ISerivceProxy 并将其传入并尝试测试服务调用以确保它正确调用 Action 的包装器:
[TestMethod]
[Asynchronous]
public void Test_Returns_Expected()
{
var mockProxy = new Mock<IServiceProxy>();
var helper = new HelperClient(mockProxy.Object);
bool expectedResult = true;
var result = new TestResponse() { Value = expectedResult };
this.mockProxy.Setup(
p => p.TestAsync(It.IsAny<TestRequest>()))
.Raises(p => p.TestCompleted += null, new TestCompletedEventArgs(new object[] { result }, null, false, null));
helper.Test(new TestRequest(), (response, ex) =>
{
Assert.AreEqual(expectedResult, response.Value);
EnqueueTestComplete();
});
}
问题是模拟的代理对象永远不会引发 TestCompleted 事件,因此我的响应操作永远不会被调用来完成测试(即使测试似乎成功完成,Assert 也不会真正运行)。抱歉这么长的帖子,只是想向您展示尽可能多的代码。
编辑 2
已添加
[Asynchronous]
并调用EnqueueTestComplete()
我意识到我可能需要让测试等待事件被引发。这并没有真正帮助,事件仍然没有引发,所以测试只是挂起并且永远不会完成。编辑 3
Aliostad 的回答是正确的,即我的设置期望的签名与实际的 Test() 签名不匹配,这允许我将响应 Action 作为第二个参数传递。愚蠢的错误,但这就是阻止 Moq 引发 Completed 事件的原因。我也忘记将 Action 作为 TestCompletedEventArgs 中的 userState 对象传递,以便在引发 Completed 事件时实际调用它。此外,
[Asynchronous]
和 EnqueueTestCompleted
在这种情况下似乎没有必要。这是任何感兴趣的人的更新测试代码:
[TestMethod]
public void Test_Returns_Expected()
{
var mockProxy = new Mock<IServiceProxy>();
var helper = new HelperClient(mockProxy.Object);
bool expectedResult = true;
var result = new TestResponse() { Value = expectedResult };
Action<TestResponse, Exception> responseAction = (response, ex) =>
{
Assert.AreEqual(expectedResult, response.Value);
};
this.mockProxy.Setup(
p => p.TestAsync(It.IsAny<TestRequest>(), It.IsAny<Action<TestResponse, Exception>>()))
.Raises(p => p.TestCompleted += null, new TestCompletedEventArgs(new object[] { result }, null, false, responseAction));
helper.Test(new TestRequest(), responseAction);
}
最佳答案
模拟事件非常痛苦,并且单元测试变得脆弱。但正如你所说,没有办法解决它。但通常您会调用您尝试测试的调用并阻止当前线程(使用 sleep 或其他方法),直到引发事件(或超时)。
实际上并不清楚你在测试什么。我可以看到一个模拟和一个响应,实际的真实对象在哪里?
我会相应地更新我的答案。
更新
我可以在这里看到一个问题:
helper.Test(new TestRequest(), (response, ex) =>
{
Assert.AreEqual(expectedResult, response.Value);
EnqueueTestComplete();
});
在最后一条语句中,您将 EnqueueTestComplete();并且您断言,但永远不会使用此操作,因为它已传递给 moq 对象。
此外,您正在设定
TestAsync(It.IsAny<TestRequest>()))
的期望值。 (一个参数)当您使用 HelperClient
中的两个参数调用它时( this.proxy.TestAsync(request, response);
),这就是为什么它永远不会被解雇,因为没有达到预期。
关于silverlight - 使用 Moq 在 Silverlight WCF 代理中模拟异步调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3841618/