1. 程式人生 > >Camel In Action 第六章 camel單元測試

Camel In Action 第六章 camel單元測試

6.2.2 使用Mock元件進行單元測試 
為了學習如何使用Mock元件,我們使用下面這個路由: 
from("jms:topic:quote").to("mock:quote"); 
這個路由消費來自JMS主題quote的訊息,並把訊息路由到一個名為quote的mock端點。 
mock端點在Camel中的實現為:org.apache.camel.component.mock.MockEndpoint類,它提供了大量的方法來設定期望。表格6.2列出了最常用的一些方法。 

使用MockEndpoint類進行單元測試: 
package camelinaction; 
import org.apache.camel.builder.RouteBuilder; 
import org.apache.camel.component.mock.MockEndpoint; 
import org.apache.camel.test.junit4.CamelTestSupport; 
public class FirstMockTest extends CamelTestSupport { 
@Override 
protected RouteBuilder createRouteBuilder() throws Exception { 
return new RouteBuilder() { 
@Override 
public void configure() throws Exception { 
from("jms:topic:quote").to("mock:quote"); 

}; 

@Test 
public void testQuote() throws Exception { 
MockEndpoint quote = getMockEndpoint("mock:quote"); 
quote.expectedMessageCount(1); 
template.sendBody("jms:topic:quote", "Camel rocks"); 
quote.assertIsSatisfied(); 



---使用CamelTestSupport類的getMockEndpoint方法獲取MockEndpoint物件; 
---設定期望值 
---開始測試:向JMS主題傳送一個訊息; 
---使用assertIsSatisfied方法驗證期望是否被滿足,如果某一個期望沒有被滿足,Camel將會丟擲異常:java.lang.AssertionError; 

注意:assertIsSatisfied方法執行的超時時間為10秒,可以使用setResultWaitTime(long timeInMillis)方法設定超時時間。、 

使用SEDA元件代替JMS元件 
程式碼列表使用了JMS元件,但是,為了使事情變得更加簡單,我們使用SEDA元件。 
SEDA元件的文件地址:
http://camel.apache.org/seda.html.


可以通過註冊SEDA元件來模擬JMS元件: 
@Override 
protected CamelContext createCamelContext() throws Exception { 
CamelContext context = super.createCamelContext(); 
context.addComponent("jms", context.getComponent("seda")); 
return context; 

覆蓋createCamelContext方法,新增SEDA元件作為jms元件,這樣做,你在路由中使用jms:元件的時候,實際上使用的是SEDA元件。 

6.2.3 驗證訊息到達的正確性 
expectedMessageCount方法只能驗證訊息到達的數量,並不能驗證到達的訊息的內容。要驗證訊息的內容,可以使用expectedBodiesReceived方法: 
@Test 
public void testQuote() throws Exception { 
MockEndpoint mock = getMockEndpoint("mock:quote"); 
mock.expectedBodiesReceived("Camel rocks"); 
template.sendBody("jms:topic:quote", "Camel rocks"); 
mock.assertIsSatisfied(); 


驗證多條訊息按順序到達: 
@Test 
public void testQuotes() throws Exception { 
MockEndpoint mock = getMockEndpoint("mock:quote"); 
mock.expectedBodiesReceived("Camel rocks", "Hello Camel"); 
template.sendBody("jms:topic:quote", "Camel rocks"); 
template.sendBody("jms:topic:quote", "Hello Camel"); 
mock.assertIsSatisfied(); 

驗證多條訊息到達(不關注順序) 
mock.expectedBodiesReceivedInAnyOrder("Camel rocks", "Hello Camel"); 

如果有很多條訊息到達,可以使用方法: 
List bodies = ... //例項化期望到達的訊息 
mock.expectedBodiesReceived(bodies);//list作為引數 


6.2.4 使用mock表示式 

假設,你期望接收到一條訊息,訊息的內容中包含單詞"Camel",下面是一種處理方式: 
@Test 
public void testIsCamelMessage() throws Exception { 
MockEndpoint mock = getMockEndpoint("mock:quote"); 
mock.expectedMessageCount(2); 
template.sendBody("jms:topic:quote", "Hello Camel"); 
template.sendBody("jms:topic:quote", "Camel rocks"); 
assertMockEndpointsSatisfied(); 
List<Exchange> list = mock.getReceivedExchanges(); 
String body1 = list.get(0).getIn() 
.getBody(String.class); 
String body2 = list.get(1).getIn() 
.getBody(String.class); 
assertTrue(body1.contains("Camel")); 
assertTrue(body2.contains("Camel")); 


---設定期望:收到兩條訊息; 
---傳送訊息; 
---確認滿足了期望:此處使用的是assertMockEndpointsSatisfied方法(一站式方法),而不是mock.assertIsSatisfied(); 方法; 
---使用getReceivedExchanges方法獲取mock:quote端點收到所有exchange,進而獲取訊息體,驗證其內容是否包含"Camel"。 
上述程式碼在傳送訊息的前後都用到了期望值:傳送訊息前 mock.expectedMessageCount(2);傳送訊息後:assertTrue(body1.contains("Camel")); 還有一種方式,只需在一個地方宣告期望。表格6.3列出了MockEndpoint類的額外的方法:在這些方法中你可以使用表示式來宣告期望。可以使用messageme方法改進上面的單元測試: 
@Test 
public void testIsCamelMessage() throws Exception { 
MockEndpoint mock = getMockEndpoint("mock:quote"); 
mock.expectedMessageCount(2); 
mock.message(0).body().contains("Camel"); 
mock.message(1).body().contains("Camel"); 
//mock.allMessages().body().contains("Camel"); 
template.sendBody("jms:topic:quote", "Hello Camel"); 
template.sendBody("jms:topic:quote", "Camel rocks"); 
assertMockEndpointsSatisfied(); 


如果你的期望是基於訊息頭的,怎麼設定呢? 
mock.message(0).header("JMSPriority").isEqualTo(4); 

上述程式碼中用到的 contains和 isEqualTo方式被稱作建造方法,用於建立期望的謂詞。表6.4列出了其他建造方法。 

6.2.5 測試訊息到達的順序 
假設你期望訊息按一定的順序到達,例如:期望訊息到達的順序是1,2,3,如果到達訊息的順序是1,3,2就是錯誤的,單元測試失敗。 
Mock元件提供了測試訊息到達的升序、降序的功能。例如:expectsAscending方法的使用: 
mock.expectsAscending(header("Counter")); 
上面一句程式碼表達的期望是:期望收到訊息的順序是升序,用header中的Counter屬性值排序。 
當你要求收到的第一個訊息的header("Counter")必須是1,你可以新增另一個期望: 
mock.message(0).header("Counter").isEqualTo(1); 
此時值為1, 2, 3, ...,或者1, 2, 4, 5, 6, 8, 10, ... 都會通過測試,因為expectsAscending方法並不要求這些值之間的步長值。 
當你要求到達的訊息中counter的值之間的步長值為固定值時,你需要自定義一個表示式。 

使用自定義表示式 
當已有的表示式和謂詞無法滿足你的需求時,你可以自定義表示式,此時你可以編寫自己的程式碼來處理期望。程式碼列表:6.8 

@Test 
public void testGap() throws Exception { 
final MockEndpoint mock = getMockEndpoint("mock:quote"); 
mock.expectedMessageCount(3); 
mock.expects(new Runnable() { 
public void run() { 
int last = 0; 
for (Exchange exchange : mock.getExchanges()) { 
int current = exchange.getIn() 
.getHeader("Counter", Integer.class); 
if (current <= last) { 
fail("Counter is not greater than last counter"); 
} else if (current - last != 1) { 
fail("Gap detected: last: " + last 
+ " current: " + current); 

last = current; 


}); 
template.sendBodyAndHeader("jms:topic:quote", "A", "Counter", 1); 
template.sendBodyAndHeader("jms:topic:quote", "B", "Counter", 2); 
template.sendBodyAndHeader("jms:topic:quote", "C", "Counter", 4); 
mock.assertIsNotSatisfied(); 


建立自定義表示式 
---使用expects方法,此方法接收實現Runnable介面的引數,你可以在實現Runnable介面的類中編寫自定義邏輯; 
---在Runnable類中,你可以遍歷收到的exchanges,進而獲取header中的Counter屬性值。 
---驗證獲取的屬性值是否升序,是否步長值為1. 


6.2.6 使用mock模擬真實的元件 
假設有一個如下路由,在路由中你使用了HTTP服務:Jetty客戶端,獲取訂單狀態。 
from("jetty:
http://web.rider.com/service/order
") 
.process(new OrderQueryProcessor()) 
.to("mina:tcp://miranda.rider.com:8123?textline=true") 
.process(new OrderResponseProcessor()); 

客戶端傳送了一個HTTP GET請求,並用訂單的ID作為查詢引數,到URL:http://web.rider.com/service/order。Camel使用OrderQueryProcessor把訊息轉換為騎士汽車零部件系統(名為米蘭達)理解的格式。接著訊息被使用TCP傳送到miranda系統,接著Camel等待響應。響應訊息在返回客戶端之前被OrderResponseProcessor處理。 

假設要你寫一個單元測試,用於測試上述路由。挑戰在於:此時你不能訪問miranda系統獲取訂單狀態,你必須模擬此係統的響應。 
Camel提供了兩種方法來模擬真實元件,見表6.5. 

你可以使用Mock元件模擬endpoint,進而使用表6.5中的方法控制響應內容。那麼,你需要Mock模擬的endpoint替換上述路由中真實的endpoint:mock:miranda。由於你準備在本地執行測試用例,你需要改成HTTP的hostname為localhost: 
from("jetty:
http://localhost
:9080/service/order") 
.process(new OrderQueryProcessor()) 
.to("mock:miranda") 
.process(new OrderResponseProcessor()); 

單元測試用例如下程式碼列表6.9: 

public class MirandaTest extends CamelTestSupport { 
private String url = "http://localhost:9080/service/order?id=123"; 
@Override 
protected RouteBuilder createRouteBuilder() throws Exception { 
return new RouteBuilder() { 
@Override 
public void configure() throws Exception { 
from("jetty:http://localhost:9080/service/order") 
.process(new OrderQueryProcessor()) 
.to("mock:miranda") 
.process(new OrderResponseProcessor()); 
@Test 
public void testMiranda() throws Exception { 
MockEndpoint mock = getMockEndpoint("mock:miranda"); 
mock.expectedBodiesReceived("ID=123"); 
mock.whenAnyExchangeReceived(new Processor() { 
public void process(Exchange exchange) throws Exception { 
exchange.getIn().setBody("ID=123,STATUS=IN PROGRESS"); 

}); 
String out = template.requestBody(url, null, String.class); 
assertEquals("IN PROGRESS", out); 
assertMockEndpointsSatisfied(); 


private class OrderQueryProcessor 
implements Processor { 
public void process(Exchange exchange) throws Exception { 
String id = exchange.getIn().getHeader("id", String.class); 
exchange.getIn().setBody("ID=" + id); 


private class OrderResponseProcessor 
implements Processor { 
public void process(Exchange exchange) throws Exception { 
String body = exchange.getIn().getBody(String.class); 
String reply = ObjectHelper.after(body, "STATUS="); 
exchange.getIn().setBody(reply); 




testMiranda方法中, 
----獲取mock:miranda endpoint, 
----設定期望:收到的訊息體中內容為"ID=123" 
----使用whenAnyExchangeReceived方法返回響應,此方法允許你使用一個自定義的processor來設定響應:"ID=123,STATUS=IN PROGRESS" 
----使用template例項的requestBody方法傳送訊息到 http://localhost:9080/service/order?id=123 端點(endpoint); 
----設定期望:響應值等於"IN PROGRESS" 

6.3 使用Mock元件模擬錯誤的發生 

你可以通過拔掉網線來測試斷網發生的錯誤,但這有點極端。我們將看看如何在單元測試中使用模擬錯誤,表6.6中列出的三種不同的技術。