【问题标题】:Mocking Asynchronous Calls in Silverlight WCF Proxy using Moq使用 Moq 在 Silverlight WCF 代理中模拟异步调用
【发布时间】:2010-10-01 17:30:24
【问题描述】:

我有一个带有 WCF 服务的典型 Silverlight 应用程序,我正在使用 slsvcutil.exe 生成标准客户端代理以与 Web 服务通信。我正在尝试编写单元测试,并且正在尝试使用 Silverlight 单元测试框架和 Moq 来模拟代理并删除服务依赖项以进行测试。

我对 Moq 非常陌生,并且在进行服务调用以模拟异步调用时自动在模拟代理上自动引发各种 Completed 事件时遇到了很多麻烦。

为了使代理“可模拟”,我为生成的代理调用及其完成的事件创建了自己的简单接口:

public interface IServiceProxy
{
    void TestAsync(TestRequest request);
    void TestAsync(TestRequest request, object userState);
    event EventHandler<TestCompletedEventArgs> TestCompleted;
}

我还对生成的代理对象进行了子类化以实现该接口:

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&lt;TResponse, Exception&gt; 回调参数而不是处理来为我的ViewModel 提供更简洁的服务接口我的 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);
}

【问题讨论】:

  • 嗨,丹,我已经更新了我的答案。您的问题是对具有与您在帮助程序类中调用的签名不同的签名的方法设置期望。

标签: silverlight wcf asynchronous proxy moq


【解决方案1】:

模拟事件非常痛苦,并且单元测试变得脆弱。但正如你所说,没有办法解决它。但通常你会调用你正在尝试测试的调用并阻塞当前线程(使用睡眠或其他方法),直到引发事件(或超时)。

实际上并不清楚您在测试什么。我可以看到一个模拟和一个响应,实际的真实对象在哪里?

我会相应地更新我的答案。

更新

我可以在这里看到一个问题:

helper.Test(new TestRequest(), (response, ex) =>
{
    Assert.AreEqual(expectedResult, response.Value);
    EnqueueTestComplete();
});

在最后一条语句中,您将 EnqueueTestComplete();并且您断言,但永远不会使用此操作,因为它已传递给 moq 对象。

此外,当您在 HelperClient (this.proxy.TestAsync(request, response);) 中使用两个参数调用它时,您正在为 TestAsync(It.IsAny&lt;TestRequest&gt;()))(一个参数)设置期望,这就是为什么它永远不会被触发,因为期望没有得到满足。

【讨论】:

  • 感谢您的帮助,我完成了忽略签名不匹配的整个问题。我编辑了上面的问题,以提供更多关于我最终如何让它工作的代码。
  • 没问题,伙计。快乐整理出来。
【解决方案2】:

刚刚搜索了mock asynchronous WCF client,发现了这个问题。

为了防止这种情况,Moq 可以.Verify() 调用 p.TestAsync()。

//will throw MockException if p.TestAsync() has never been called.
this.mockProxy.Verify(p => p.TestAsync(It.IsAny<TestRequest>()), Times.Once());

【讨论】:

    猜你喜欢
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-04
    • 2019-05-22
    • 1970-01-01
    相关资源
    最近更新 更多