Camel In Action 第六章 camel單元測試
阿新 • • 發佈:2019-01-22
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中列出的三種不同的技術。
為了學習如何使用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元件的文件地址:
可以通過註冊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:
.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:
.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中列出的三種不同的技術。