2011-05-31 58 views
6

我有一些麻煩使用Moq。單元測試之後會拋出異常,即使將調用相應的方法。Moq驗證方法失敗,即使方法將被稱爲

[TestMethod] 
public void CreateFinishTest() { 
    // mock methods 
    factoryMock.Setup(f => f.LoadPlan("TestPlanDoNotUse")).Returns(testPlan).Verifiable(); 
    factoryMock.Setup(f => f.CreateFinish(It.IsAny<CreateFinishMessage>(), It.IsAny<string>())).Returns(testFinish.Id).Verifiable(); 

    try { 
     var cfm = new CreateFinishMessage() { 
      ClientId = 11, 
      MessageId = 23456, 
      CustomerId = 6, 
      FinishName = "MyFinish", 
      PlanId = "TestPlanDoNotUse" 
     }; 
     var cmd = sysCfg.Executor.CreateFinish(cfm); // calls LoadPlan with cfm.PlanId and CreateFinish with cfm and cfm.PlanId 
     sysCfg.Executor.Execute(cmd); 

     factoryMock.Verify(f => f.LoadPlan("TestPlanDoNotUse"), Times.Exactly(1)); 
     factoryMock.Verify(f => f.CreateFinish(It.IsAny<CreateFinishMessage>(), It.IsAny<string>()), Times.Exactly(1)); 
    } catch (Exception exc) { 
     Assert.Fail(exc.Message); 
    } 
} 

會出現此錯誤:

Expected invocation on the mock exactly 1 times, but was 0 times: f => f.LoadPlan("TestPlanDoNotUse") 

Configured setups: 
f => f.LoadPlan("TestPlanDoNotUse"), Times.Once 

Performed invocations: 
IFactory.LoadPlan("TestPlanDoNotUse") 
Factory.CreateFinish(IndiValue.LiveMarket.IndiCore.Communication.MessagingFormat.CreateFinishMessage, "MyFinish") 

我試過幾個不同的驗證的調用,但它不會工作。發生的錯誤似乎很混亂,它說LoadPlan("TestPlanDoNotUse")從來沒有被調用過,但它列出@ Performed invocations。

問題解決了:

我想我找到了問題,它不是一個起訂量的問題。在sysCfg.Executor.CreateFinish(cfm)中創建並啓動了一個新線程。此線程尚未完成,因此factoryMock.Verify(...)失敗。

我用AutoResetEvents:

// create AutoResetEvent triggers 
AutoResetEvent m_testTrigger1 = new AutoResetEvent(false); 

// mock methods  
factoryMock.Setup(f => f.LoadPlan(It.IsAny<string>())).Returns(testPlan).Callback(() => m_testTrigger1.Set()); 

// do something 

// wait for triggers 
bool didReturn1 = m_testTrigger1.WaitOne(timeOut); 

回答

6

在未被調用的Verifiable上,重要的是您期望中的參數與生產代碼正在使用的參數相匹配。

關於Thread.Sleep的使用,請儘量避免使用它,因爲它只會減慢測試以滿足您最慢的機器。我通常會在測試中引入WaitHandles,以確保測試的運行速度與代碼一樣快。

取一個使用WaitHandles事件的peek here on a small utility

+0

我使用了AutoResetEvents: – Robar 2011-06-27 06:29:58

6

你通常不會與驗證(表達式,時間)方法一起選擇在你的設置使用可驗證()。如果您刪除.Verifiable()調用,它會起作用嗎?

+0

如果刪除了.Verifiable()調用我得到以下錯誤:'預期調用模擬正好1次,但是0次:f => f.CreateFinish(It.IsAny (),It.IsAny ()) 配置的setu PS: F => f.CreateFinish(It.IsAny (),It.IsAny ()),Times.Never 演出調用: IFactory.LoadPlan( 「TestPlanDoNotUse」)' – Robar 2011-05-31 08:44:11

+0

這看起來有點更好,如果這確實是整個錯誤信息。現在看起來像CreateFinish不會被調用。它是否說過爲CreateFinish執行的調用? – TheFogger 2011-05-31 09:00:26

+0

不,它沒有說關於CreateFinish的invoaction的任何事情,但我調試了它,我想我找到了問題:看到我的答案。感謝您的幫助 – Robar 2011-05-31 09:34:26

2

我想這是我的答案,但我相信這是一個比許多前面提到的更簡單的解決方案。

我實現了一個WaitFor的功能,其利用一個lambda回調來計算條件:

public static void WaitFor(Func<bool> action, long timeoutMillis = 10000) { Stopwatch elapsed = Stopwatch.StartNew(); elapsed.Start(); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while (!action()) { if (elapsed.ElapsedMilliseconds > timeoutMillis) { throw new TimeoutException("Timed out waiting for condition to become true after " + elapsed.ElapsedMilliseconds + " ms"); } Thread.Sleep(0); } }

和測試代碼看起來是這樣的:

[Test] 
    public void ShouldNackUnparsableInboundMessage() 
    { 
     var nackCalled = false; 
     _mock.Setup(m => m.BasicNack(999, false, false)).Callback(() => 
     { 
      nackCalled = true; 
     }); 

     ... do whatever which invokes the call on another thread. 

     WaitFor(() => nackCalled); 
     // Test will fail with timeout if BasicNack is never called. 
    }