一.簡(jiǎn)介
1.是什么
WireMock是一個(gè)基于http api的模擬器。有些人可能認(rèn)為它是服務(wù)虛擬化工具或模擬服務(wù)器渊鞋。
它使您能夠在依賴的API不存在或不完整時(shí)保持高效。它的構(gòu)建速度很快,可以將構(gòu)建時(shí)間從幾個(gè)小時(shí)減少到幾分鐘橡淆。
2.能做什么
可以做單元測(cè)試,獨(dú)立的 JAR 可以做前后端分離開發(fā)&聯(lián)調(diào)使用.
二.版本簡(jiǎn)介
WireMock有兩種版本:
- 一個(gè)標(biāo)準(zhǔn)的JAR只包含WireMock;
- 一個(gè)獨(dú)立的JAR包含WireMock及其所有依賴項(xiàng).
在某些情況下 WireMock 可能會(huì)與某些代碼中的包產(chǎn)生沖突.
獨(dú)立的 JAR 所依賴的包被隱藏在包中,與項(xiàng)目本身的代碼相對(duì)獨(dú)立.
說(shuō)白了,標(biāo)準(zhǔn)的 WireMock 是與測(cè)試代碼耦合在一起的運(yùn)行在同一個(gè)進(jìn)程同一個(gè)JVM 中.獨(dú)立的WireMock 包是運(yùn)行在另外的進(jìn)程中,也是獨(dú)立的 JVM.
目前召噩,建議您使用獨(dú)立JAR作為Spring引導(dǎo)項(xiàng)目的依賴項(xiàng)。這避免了關(guān)于 Jetty 版本的沖突.
三. MAVEN
要將標(biāo)準(zhǔn)WireMock JAR作為項(xiàng)目依賴項(xiàng)添加到POM的依賴項(xiàng)部分:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.19.0</version>
<scope>test</scope>
</dependency>
或使用獨(dú)立JAR:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.19.0</version>
</dependency>
四.JUnit 4.x 用法
JUnit規(guī)則提供了在測(cè)試用例中包含WireMock的方便方法逸爵。它為您處理生命周期具滴,在每個(gè)測(cè)試方法之前啟動(dòng)服務(wù)器,然后停止师倔。
1.基本用法
在默認(rèn)的端口(8080)啟用WireMock.
@Rule
public WireMockRule wireMockRule = new WireMockRule();
WireMockRule 通過(guò)構(gòu)造函數(shù)來(lái)設(shè)置.設(shè)置項(xiàng)通過(guò)Options實(shí)例來(lái)創(chuàng)建:
@Rule
public WireMockRule wireMockRule = new WireMockRule(options().port(8888).httpsPort(8889));
詳細(xì)配置信息[http://wiremock.org/docs/configuration/]
2.不匹配的請(qǐng)求
JUnit規(guī)則將驗(yàn)證在測(cè)試用例過(guò)程中接收到的所有請(qǐng)求都是由配置的存根(而不是默認(rèn)的404)提供服務(wù)的构韵。如果沒(méi)有拋出驗(yàn)證異常,則測(cè)試失敗趋艘。這個(gè)行為可以通過(guò)傳遞一個(gè)額外的構(gòu)造函數(shù)標(biāo)志來(lái)禁用:
@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ī)則訪問(wèn) stub & 驗(yàn)證DSL
除了WireMock類上的靜態(tài)方法外疲恢,還可以通過(guò)規(guī)則對(duì)象直接配置stubs 。
這樣做有兩個(gè)好處:
1)它更快瓷胧,因?yàn)樗苊饬送ㄟ^(guò)HTTP發(fā)送命令;
2)如果你想模擬多個(gè)服務(wù)显拳,你可以為每個(gè)服務(wù)聲明一個(gè)規(guī)則,但不需要為每個(gè)服務(wù)創(chuàng)建一個(gè)客戶端對(duì)象搓萧。
@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 實(shí)戰(zhàn)
測(cè)試標(biāo)準(zhǔn)的 WireMock 包,原理是啟動(dòng)測(cè)試模塊之前先啟動(dòng)一個(gè)內(nèi)置的 MockServer.
寫個(gè) Service 要調(diào)用遠(yuǎn)程服務(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)建時(shí)間為 下午2:52-2018/11/21
* 項(xiàng)目名稱 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();
}
}
編寫單元測(cè)試
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)建時(shí)間為 下午2:53-2018/11/21
* 項(xiàng)目名稱 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());
}
}
測(cè)試結(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ù)器可以在自己的進(jìn)程中運(yùn)行杂数,并通過(guò)Java API、HTTP或JSON文件對(duì)其進(jìn)行配置矛绘。
Once you have downloaded the standalone JAR you can run it simply by doing this:
$ java -jar wiremock-standalone-2.19.0.jar
命令行選項(xiàng)
以下可以在命令行中指定:
--port:設(shè)置HTTP端口號(hào)耍休,例如端口9999。使用--port 0來(lái)動(dòng)態(tài)確定端口.
--https-port:如果指定货矮,則在提供的端口上啟用HTTPS.
--bind-address:WireMock服務(wù)器應(yīng)該提供的IP地址羊精。如果未指定,則綁定到所有本地網(wǎng)絡(luò)適配器囚玫。
--https-keystore:包含用于HTTPS的SSL證書的密鑰存儲(chǔ)文件的路徑喧锦。密鑰存儲(chǔ)庫(kù)的密碼必須為“password”。此選項(xiàng)僅在指定--https-port時(shí)才有效抓督。如果沒(méi)有使用此選項(xiàng)燃少,WireMock將默認(rèn)為它自己的自簽名證書。
六.與 SpringBoot 整合
Spring Cloud Contract 已經(jīng)創(chuàng)建了一個(gè)庫(kù)來(lái)支持使用“ambient”HTTP服務(wù)器運(yùn)行WireMock铃在。它還簡(jiǎn)化了配置的某些方面阵具,并消除了在同時(shí)運(yùn)行Spring引導(dǎo)和WireMock時(shí)出現(xiàn)的一些常見問(wèn)題。
場(chǎng)景描述:項(xiàng)目有兩個(gè)模塊 A&B, 現(xiàn)在需要我負(fù)責(zé)模塊 A,B 模塊由另外一個(gè)人負(fù)責(zé), 整個(gè)項(xiàng)目的運(yùn)行需要 A 調(diào)用 B 的接口模塊.但是運(yùn)行單元測(cè)試的時(shí)候就需要 B 能夠調(diào)通, 這個(gè)時(shí)候需要一個(gè)模塊能夠模擬 B 的接口.
1.首先添加依賴,屬于 SpringCloud,可以直接添加 SpringCloud 依賴,會(huì)自動(dòng)整合合適的版本.
<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 代碼
這個(gè)部分會(huì)通過(guò) RestTemplate 調(diào)用遠(yuǎn)程摸個(gè)模塊.
@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.編寫單元測(cè)試
@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"));
}
}
重點(diǎn)來(lái)了,
運(yùn)行 dev 配置文件:
application-dev.properties
remote=http://127.0.0.1:8080/hello
把遠(yuǎn)程訪問(wèn)的地址改為本地
在 resources 下面
建立mappings文件夾,里面存入如下信息,文件以 .json 結(jié)尾:
{
"request" : {
"urlPath" : "/hello",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "Hello World"
}
}
@AutoConfigureWireMock(stubs="classpath:/mappings")表示在classpath:/mappings文件夾下添加一些映射,每個(gè) json 文件都是一個(gè)映射. json文件可以指定訪問(wèn)方式,訪問(wèn)的路徑,返回?cái)?shù)據(jù),狀態(tài)碼等等.