2010-10-01 135 views
7

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

我对Moq非常陌生,遇到很多麻烦,自动提升各种完成模拟异步呼叫的服务调用时,模拟代理会自动完成事件。

为了使代理“mockable”我已经创建了自己的简单的界面生成的代理调用及其完成的事件:

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 
} 

看起订量的文档后,这是我在尝试建立模拟期待的TestAsync()调用,并立即与结果EventArgs的提高TestCompleted事件:

[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<TResponse, Exception>回调参数,而不是处理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事件,所以我的反应动作永远不会得到调用来完成测试(尽管测试显示成功的完成断言是ne实际上运行)。对不起,这么长的帖子,只是试图向你展示尽可能多的代码。

EDIT 2

新增[Asynchronous],并打电话给其EnqueueTestComplete()我意识到我可能需要进行测试等待要引发的事件。这并没有真正的帮助,事件仍然没有提出,所以测试只是挂起,并没有完成。

编辑3

Aliostad的答案是正确的,我的设置期望的签名不符合实际的测试()的签名让我在响应行动作为第二PARAM通过。愚蠢的错误,但这正是阻止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); 
} 
+1

嗨丹,我已经更新了我的答案。你的问题是设置一个与你在助手类中调用的签名不同的方法的期望值。 – Aliostad 2010-10-02 10:24:38

回答

4

惩戒事件是一个相当痛苦和单元测试变脆。但正如你所说,它无法绕过它。但是,通常情况下,您会进行要尝试的调用并阻止当前线程(使用Sleep或其他方法),直到引发事件(或超时)。

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

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

UPDATE

我可以看到这里有一个问题:在最后的陈述

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

,你是把EnqueueTestComplete();你断言,但这个动作永远不会被使用,因为它传递给moq对象。

而且你设置TestAsync(It.IsAny<TestRequest>()))(一个参数)的期望,而你是在HelperClientthis.proxy.TestAsync(request, response);)有两个参数调用它,这就是为什么它永远不会被解雇,因为期望没有得到满足。

+0

感谢您的帮助,我完成忽略了整个问题的签名不匹配。我编辑了上面的问题,提供了一些关于如何最终实现它的代码。 – 2010-10-04 17:30:41

+0

没问题的队友。开心整理。 – Aliostad 2010-10-04 20:04:25

0

刚刚搜索模拟异步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());