使用 Moq 在 Silverlight WCF 代理中模拟异步调用

发布于 2024-09-25 10:08:53 字数 4722 浏览 3 评论 0原文

我有一个带有 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
}

一切都构建良好,但是当我尝试使用模拟运行任何类型的测试并设置断点时,TestCompleted当我调用 TestAsync() 时,事件永远不会被引发。

关于在 Silverlight 中模拟这些类型的异步服务代理,我是否遗漏了任何明显的内容或任何更好的想法?

谢谢!

编辑:

更清楚地说,我实际上想要测试的是我制作的一个辅助类,它采用 IServiceProxy 实例并为我的 ViewModel 使用提供更清晰的服务接口通过接受 Action 回调参数而不是处理 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 并将其传递进去,然后尝试测试服务调用以确保包装器正确调用操作:

[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

阿利斯塔德的答案是正确的,我的设置期望的签名与实际的 Test() 签名不匹配,允许我传递响应操作作为第二个参数。愚蠢的错误,但这就是阻止 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);
}

I have a typical Silverlight application with a WCF service and I am using slsvcutil.exe to generate the standard client proxy to communicate with the web service. I am trying to write unit tests and I'm attempting to use the Silverlight Unit Testing framework and Moq to mock the proxy and remove the service dependency for testing.

I am very new to Moq and having a lot of trouble automatically raising the various Completed events on the mocked proxy automatically when service calls are made to simulate the async calls.

In order to make the proxy "mockable" I've created my own simple interface for the generated proxy calls and their completed events:

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

I also subclassed the generated proxy object to implement that interface:

public class MyServiceProxy : GeneratedServiceClient, IServiceProxy, ICommunicationObject
{
    // ... overloaded proxy constructors
}

After looking at the Moq documentation, this is how I am attempting to set up the mock to expect the TestAsync() call and immediately raise the TestCompleted event with the result in the 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
}

Everything builds fine, but when I attempt to run any kind of test using the mock and set break points the TestCompleted event is never being raised when I call TestAsync().

Is there anything obvious that I am missing or any better ideas about mocking these types of async service proxies in Silverlight?

Thanks!

EDIT:

To be more clear what I am actually trying to test is a helper class I made which takes an instance of IServiceProxy and provides a cleaner service interface for my ViewModel to use by accepting Action<TResponse, Exception> callback parameters rather than dealing with callback events in my ViewModel. I understand how I could mock this as well to directly test my ViewModel but I figured it would be nice to test the helper class by itself first.

Here is an example of what I am talking about:

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);
            }
        }
    }
}

So in my test what I am really doing is mocking ISerivceProxy and passing it in and just attempting to test a service call to make sure the wrapper it invoking the Action correctly:

[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();
    });
}

The problem is that the mocked proxy object is never raising the TestCompleted event so my response action is never getting invoked to finish the test (even though the test appears to complete successfully the Assert is never actually run). Sorry for such a long post, just trying to show you as much code as possible.

EDIT 2

Added [Asynchronous] and call to EnqueueTestComplete() which I realized I may need to make the test wait for the event to be raised. This did not really help, the event is still never raised so the test just hangs and never completes.

EDIT 3

Aliostad's answer was correct that my setup expectation's signature did not match the actual Test() signature allowing me to pass in a response Action as the second param. Stupid mistake, but that is what was preventing Moq from raising the Completed event. I was also forgetting to pass the Action in as the userState object in the TestCompletedEventArgs so that it would actually be invoked when the Completed event was raised. Also, the [Asynchronous] and EnqueueTestCompleted did not seem to be necessary in this case.

Here is updated test code for anyone interested:

[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);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

骑趴 2024-10-02 10:08:53

模拟事件非常痛苦,单元测试变得脆弱。但正如你所说,没有办法解决这个问题。但通常您会进行尝试测试的调用并阻止当前线程(使用 Sleep 或其他方法),直到引发事件(或超时)。

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

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

更新

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

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

在最后一个语句中,您将 EnqueueTestComplete();并且您断言但此操作永远不会被使用,因为它被传递到最小起订量对象。

此外,当您在 HelperClient (< code>this.proxy.TestAsync(request, response);) 这就是为什么它永远不会被触发,因为没有满足期望。

Mocking events is quite a pain and unit tests become brittle. But as you said there is no way around it. But normally you would make the call you are trying to test and block the current thread (using Sleep or other methods) until event is raised (or a time-out).

It is actually not clear what you are testing. I can see a mock and a response, where is the actual real object?

I will update my answer accordingly.

UPDATE

I can see a problem here:

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

in the last statement, you are putting EnqueueTestComplete(); and you assert but this action will never be used because it is passed to the moq object.

Also you are setting the expectation for TestAsync(It.IsAny<TestRequest>())) (one argument) while you are calling it with two arguments in the HelperClient (this.proxy.TestAsync(request, response);) and that is why it never gets fired since expectation is not met.

走走停停 2024-10-02 10:08:53

刚刚搜索模拟异步WCF客户端并发现了这个问题。

为了防止这种情况,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());

just searched for mock asynchronous WCF client and found this question.

to prevent this situation Moq can .Verify() that p.TestAsync() has been invoked.

//will throw MockException if p.TestAsync() has never been called.
this.mockProxy.Verify(p => p.TestAsync(It.IsAny<TestRequest>()), Times.Once());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文