一.簡介
1.是什么
WireMock是一個基于http api的模擬器。有些人可能認為它是服務(wù)虛擬化工具或模擬服務(wù)器。
它使您能夠在依賴的API不存在或不完整時保持高效。它的構(gòu)建速度很快,可以將構(gòu)建時間從幾個小時減少到幾分鐘。
2.能做什么
可以做單元測試,獨立的 JAR 可以做前后端分離開發(fā)&聯(lián)調(diào)使用.
二.版本簡介
WireMock有兩種版本:
- 一個標準的JAR只包含WireMock;
- 一個獨立的JAR包含WireMock及其所有依賴項.
在某些情況下 WireMock 可能會與某些代碼中的包產(chǎn)生沖突.
獨立的 JAR 所依賴的包被隱藏在包中,與項目本身的代碼相對獨立.
說白了,標準的 WireMock 是與測試代碼耦合在一起的運行在同一個進程同一個JVM 中.獨立的WireMock 包是運行在另外的進程中,也是獨立的 JVM.
目前,建議您使用獨立JAR作為Spring引導項目的依賴項。這避免了關(guān)于 Jetty 版本的沖突.
三. MAVEN
要將標準WireMock JAR作為項目依賴項添加到POM的依賴項部分:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.19.0</version>
<scope>test</scope>
</dependency>
或使用獨立JAR:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.19.0</version>
</dependency>
四.JUnit 4.x 用法
JUnit規(guī)則提供了在測試用例中包含WireMock的方便方法。它為您處理生命周期,在每個測試方法之前啟動服務(wù)器,然后停止。
1.基本用法
在默認的端口(8080)啟用WireMock.
@Rule
public WireMockRule wireMockRule = new WireMockRule();
WireMockRule 通過構(gòu)造函數(shù)來設(shè)置.設(shè)置項通過Options實例來創(chuàng)建:
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8888).httpsPort(8889));
詳細配置信息[http://wiremock.org/docs/configuration/]
2.不匹配的請求
JUnit規(guī)則將驗證在測試用例過程中接收到的所有請求都是由配置的存根(而不是默認的404)提供服務(wù)的。如果沒有拋出驗證異常,則測試失敗。這個行為可以通過傳遞一個額外的構(gòu)造函數(shù)標志來禁用:
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8888), false);
3.其他@Rule配置
@ClassRule
public static WireMockClassRule wireMockRule = new WireMockClassRule(8089);
@Rule
public WireMockClassRule instanceRule = wireMockRule;
4.從規(guī)則訪問 stub & 驗證DSL
除了WireMock類上的靜態(tài)方法外,還可以通過規(guī)則對象直接配置stubs 。
這樣做有兩個好處:
1)它更快,因為它避免了通過HTTP發(fā)送命令;
2)如果你想模擬多個服務(wù),你可以為每個服務(wù)聲明一個規(guī)則,但不需要為每個服務(wù)創(chuàng)建一個客戶端對象。
@Rule
public WireMockRule service1 = new WireMockRule(8081);
@Rule
public WireMockRule service2 = new WireMockRule(8082);
@Test
public void bothServicesDoStuff() {
service1.stubFor(get(urlEqualTo("/blah")).....);
service2.stubFor(post(urlEqualTo("/blap")).....);
...
}
五.JUnit 4.x 實戰(zhàn)
測試標準的 WireMock 包,原理是啟動測試模塊之前先啟動一個內(nèi)置的 MockServer.
寫個 Service 要調(diào)用遠程服務(wù),地址為 http://127.0.0.1:8080/hello
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* <p>
* 創(chuàng)建時間為 下午2:52-2018/11/21
* 項目名稱 SpringBootWireMock
* </p>
*
* @author shao
* @version 0.0.1
* @since 0.0.1
*/
@Service
public class RemoteService {
private RestTemplate restTemplate = new RestTemplate();
public String access(){
ResponseEntity<String> result = restTemplate.getForEntity("http://127.0.0.1:8080/hello", String.class);
System.out.println(result);
return result.getBody();
}
}
編寫單元測試
import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
/**
* <p>
* 創(chuàng)建時間為 下午2:53-2018/11/21
* 項目名稱 SpringBootWireMock
* </p>
*
* @author shao
* @version 0.0.1
* @since 0.0.1
*/
@RunWith(SpringRunner.class)
@SpringBootTest
//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RemoteServiceTest {
// Start WireMock on some dynamic port
// for some reason `dynamicPort()` is not working properly
@ClassRule
public static WireMockClassRule wiremock = new WireMockClassRule(options().port(8080));
// A service that calls out over HTTP to localhost:${wiremock.port}
@Autowired
private RemoteService service;
@Test
public void access() throws Exception {
// Stubbing WireMock
wiremock.stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("Hello World!")));
// We're asserting if WireMock responded properly
System.out.println(service.access());
}
}
測試結(jié)果
2018-11-21 16:10:32.096 INFO 2105 --- [ main] c.h.s.service.RemoteServiceTest : Started RemoteServiceTest in 7.554 seconds (JVM running for 12.989)
2018-11-21 16:10:32.662 INFO 2105 --- [qtp609887969-18] o.e.j.s.handler.ContextHandler.ROOT : RequestHandlerClass from context returned com.github.tomakehurst.wiremock.http.StubRequestHandler. Normalized mapped under returned 'null'
<200,Hello World!,{Content-Type=[text/plain], Matched-Stub-Id=[a00a6350-bf66-4d89-969d-c5a8cb49c3c0], Vary=[Accept-Encoding, User-Agent], Transfer-Encoding=[chunked], Server=[Jetty(9.4.12.v20180830)]}>
Hello World!
2018-11-21 16:10:33.013 INFO 2105 --- [ main] o.e.jetty.server.AbstractConnector : Stopped NetworkTrafficServerConnector@797501a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
WireMock服務(wù)器可以在自己的進程中運行,并通過Java API、HTTP或JSON文件對其進行配置。
Once you have downloaded the standalone JAR you can run it simply by doing this:
$ java -jar wiremock-standalone-2.19.0.jar
命令行選項
以下可以在命令行中指定:
--port:設(shè)置HTTP端口號,例如端口9999。使用--port 0來動態(tài)確定端口.
--https-port:如果指定,則在提供的端口上啟用HTTPS.
--bind-address:WireMock服務(wù)器應(yīng)該提供的IP地址。如果未指定,則綁定到所有本地網(wǎng)絡(luò)適配器。
--https-keystore:包含用于HTTPS的SSL證書的密鑰存儲文件的路徑。密鑰存儲庫的密碼必須為“password”。此選項僅在指定--https-port時才有效。如果沒有使用此選項,WireMock將默認為它自己的自簽名證書。
六.與 SpringBoot 整合
Spring Cloud Contract 已經(jīng)創(chuàng)建了一個庫來支持使用“ambient”HTTP服務(wù)器運行WireMock。它還簡化了配置的某些方面,并消除了在同時運行Spring引導和WireMock時出現(xiàn)的一些常見問題。
場景描述:項目有兩個模塊 A&B, 現(xiàn)在需要我負責模塊 A,B 模塊由另外一個人負責, 整個項目的運行需要 A 調(diào)用 B 的接口模塊.但是運行單元測試的時候就需要 B 能夠調(diào)通, 這個時候需要一個模塊能夠模擬 B 的接口.
1.首先添加依賴,屬于 SpringCloud,可以直接添加 SpringCloud 依賴,會自動整合合適的版本.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
2.編寫Controller代碼
@RestController
public class WireMockController {
@Autowired
private RemoteService service;
@GetMapping("get")
public String get() {
return service.access();
}
}
3.編寫 Service 代碼
這個部分會通過 RestTemplate 調(diào)用遠程摸個模塊.
@Service
public class RemoteService {
@Autowired
private RestTemplate restTemplate;
@Value("${remote}")
private String url;
public String access() {
ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
System.out.println(result);
return result.getBody();
}
}
4.編寫單元測試
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("dev")
@RunWith(SpringRunner.class)
@AutoConfigureWireMock(stubs="classpath:/mappings")
public class WireMockControllerTest {
@Autowired
private MockMvc mvc;
@Rule
public WireMockRule service1 = new WireMockRule(8090);
@Test
public void bothServicesDoStuff() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/get"))
.andExpect(status().isOk())
.andExpect(content().string("Hello World"));
}
}
重點來了,
運行 dev 配置文件:
application-dev.properties
remote=http://127.0.0.1:8080/hello
把遠程訪問的地址改為本地
在 resources 下面
建立mappings文件夾,里面存入如下信息,文件以 .json 結(jié)尾:
{
"request" : {
"urlPath" : "/hello",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "Hello World"
}
}
@AutoConfigureWireMock(stubs="classpath:/mappings")表示在classpath:/mappings文件夾下添加一些映射,每個 json 文件都是一個映射. json文件可以指定訪問方式,訪問的路徑,返回數(shù)據(jù),狀態(tài)碼等等.