2010-10-01 154 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());