一、關鍵概念
- spy:監聽調用過程,不具備轉發能力,主要是監聽調用過程(類似抓包,F12的功能)
- stub:返回固定值的實現,無法在測試中進行動態變更(指無法根據真實的值進行動態變更),比較死板(類似Charles的map local功能,不經過后端,類似擋板)
- proxy:使用代理協議轉發請求并返回真實內容,可以轉發、監聽,甚至修改(類似Charles的rewrite功能,把請求轉發給真實的服務,服務返回response后,對response進行一些修改后轉發給前端)
- fake:用假的實現代替真的實現,但是實現中做了些捷徑(比如一個大集群下某個服務出故障,需要很長時間去修復,這個時候可以寫一個簡版的邏輯進行替代,通常是開發做的)
- mock:由mock庫動態創建的,能提供類似spy、stub、proxy的功能。mock是一種特殊的fake,強調的是可控
- mock on stub:直接返回固定值數據
- mock on proxy:利用代理轉發并修改返回數據
在這里插入圖片描述
二、應用場景
一、stub應用場景:
- Moco:https://github.com/dreamhead/moco
- 輕量級stub框架,用命令行進行啟動,方便在服務端操作
二、fake應用場景:
- H2 Database Engine:輕量級的內存Sql,不會占用資源,是JDBC的api,操作方式和mysql之類的一致。如果是試運行階段或者重點關注重點不在db層,可以使用這個框架。
三、mock應用場景:
- Wire Mock:目前一款較為流行的mock框架,社區很活躍,寫法也很優雅,功能和Charles差不多,但是更靈活,因為可以進行編碼
- 官網:http://wiremock.org/
- 下面我們重點介紹下這款框架的基礎配置和使用
1. 引入依賴
<groupId>org.example</groupId>
<artifactId>wiremock_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console-standalone</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.31.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.lightbody.bmp</groupId>
<artifactId>browsermob-core</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<includes>
<include>**</include>
</includes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
2. 基礎demo
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public class Demo_01_Base {
@Test
public void start() {
int port = 8089;
// 實例化wirmockServer對象
WireMockServer wireMockServer = new WireMockServer(
wireMockConfig()
// 配置端口號
.port(port)
// 配置全局response模板
.extensions(new ResponseTemplateTransformer(true))
);
// 啟動mock服務
wireMockServer.start();
// 這邊需要再次設置一下端口號,否則會報錯
configureFor(port);
// 配置mock服務中的一個stub,類似Charles的mapLocal功能
stubFor(get(urlEqualTo("/some/thing"))
// 配置返回的body,header,statusCode
.willReturn(
aResponse()
.withStatus(200)
.withHeader("content-Type", "application/json")
.withBody("this is my first wiremock demo!")
));
System.out.println("http://localhost:" + port);
// 等待10s,如果不等待就會直接停止服務,就啟動不了了
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wiremock復位
WireMock.reset();
// wiremock停止(不停止下一次就無法進行調用了)
wireMockServer.stop();
}
}
3. 常用的請求體的配置
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
public class Demo_02_RequestMatching {
@Test
public void start() {
int port = 8090;
// 實例化wirmockServer對象
WireMockServer wireMockServer = new WireMockServer(
wireMockConfig()
// 配置端口號
.port(port)
// 配置全局response模板
.extensions(new ResponseTemplateTransformer(true))
);
// 啟動mock服務
wireMockServer.start();
// 這邊需要再次設置一下端口號,否則會報錯
configureFor(port);
/**
* 配置mock服務中的一個stub,類似Charles的mapLocal功能
* 這些配置是“且”的關系,必須全部滿足,才能請求成功
*/
// url的路徑等于"everything"’
stubFor(any(urlPathEqualTo("everything"))
//通過header匹配規則
.withHeader("Accept", containing("xml"))
//通過cookie匹配規則
.withCookie("session", matching(".*12345.*"))
//通過QueryParam匹配規則
.withQueryParam("search_term", equalTo("WireMock"))
//通過withBasicAuth匹配規則
.withBasicAuth("jeff@example.com", "jeffteenjefftyjeff")
//通過RequestBody匹配規則
.withRequestBody(matchingJsonPath("$.a", equalTo("1")))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody("pass!")));
System.out.println("http://localhost:" + port);
// 等待10s,如果不等待就會直接停止服務,就啟動不了了
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wiremock復位
WireMock.reset();
// wiremock停止(不停止下一次就無法進行調用了)
wireMockServer.stop();
}
}
做個測試
public class Demo_02_Test_RequestMatching {
@Test
void testRequestMatching() {
given().log().all()
.auth().preemptive().basic("jeff@example.com", "jeffteenjefftyjeff")
.header("Accept", "xml")
.cookie("session", "123456")
.body("{\"a\":1,\"b\":2}")
.queryParam("search_term", "WireMock")
.when()
.post("http://localhost:8099/everything").
then().log().all()
.extract();
}
}
請求成功!
在這里插入圖片描述
- 查看wireMock的管理后臺:ip:port/__admin
在這里插入圖片描述
4. 常用的響應體的配置
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
/**
* 常用的響應的配置
*/
public class Demo_03_Response {
@Test
public void start() {
int port = 8091;
// 實例化wirmockServer對象
WireMockServer wireMockServer = new WireMockServer(
wireMockConfig()
.port(port)
.extensions(new ResponseTemplateTransformer(true))
);
// 啟動mock服務
wireMockServer.start();
configureFor(port);
stubFor(get(urlEqualTo("/some/thing"))
// 配置返回的body,header,statusCode
.willReturn(
aResponse()
.withStatus(200)
.withHeader("content-Type", "application/json")
.withBody("this is my first wiremock demo!")
));
// ok()表示返回響應狀態碼為200
stubFor(delete("/fine")
.willReturn(ok()));
// 返回響應狀態碼+body
stubFor(get("/fineWithBody")
.willReturn(ok("body")));
// 返回響應體為json格式
stubFor(get("/returnJson")
.willReturn(okJson("{\"status\":\"success\"}")));
// 進行請求重定向
stubFor(get("/redirect")
.willReturn(temporaryRedirect("/new/place")));
// 未鑒權
stubFor(get("/unauthorized")
.willReturn(unauthorized()));
// 配置響應狀態碼
stubFor(get("/statusCode")
.willReturn(status(418)));
System.out.println("http://localhost:" + port);
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wiremock復位
WireMock.reset();
// wiremock停止(不停止下一次就無法進行調用了)
wireMockServer.stop();
}
}
5. 匹配優先級
/**
* 優先級匹配:
* 1,匹配優先級高的
* 2,再按照url進行匹配
*/
public class Demo_04_Priority {
@Test
public void start() {
int port = 8093;
// 實例化wirmockServer對象
WireMockServer wireMockServer = new WireMockServer(
wireMockConfig()
.port(port)
.extensions(new ResponseTemplateTransformer(true))
);
// 啟動mock服務
wireMockServer.start();
configureFor(port);
// 匹配特定的
stubFor(get(urlEqualTo("/some/thing")).atPriority(2)
// 配置返回的body,header,statusCode
.willReturn(
aResponse()
.withStatus(200)
.withHeader("content-Type", "application/json")
.withBody("this is my first wiremock demo!")
));
// 使用正則進行通配
stubFor(get(urlMatching("/some/.*")).atPriority(12)
.willReturn(
aResponse()
.withStatus(401)
.withBody("match any")
));
// 兜底邏輯
stubFor(any(anyUrl()).atPriority(1)
.willReturn(
aResponse()
.withStatus(402)
.withBody("no match")
));
System.out.println("http://localhost:" + port);
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wiremock復位
WireMock.reset();
// wiremock停止(不停止下一次就無法進行調用了)
wireMockServer.stop();
}
}
6. 錄制和回放
wireMock也提供界面,進行錄制和回放,功能類似于Charles里面的save response
- 具體操作:
在這個界面輸入你想要錄制的網址,然后點擊錄制選項
-
新開一個頁面,輸入http://ip:port,會自動路由到你輸入的網址,這個時候你可以在頁面進行一些操作
在這里插入圖片描述
ps:跑之前切記一定要去resources文件夾里新建mappings文件夾!!否則是無法進行錄制的!!我找了好久才找到這個原因!! -
點擊stop停止錄制
在這里插入圖片描述
這個時候你會在resources目錄里面看到mappings文件,里面放著錄制好的請求
在這里插入圖片描述 -
我們可以修改里面的內容
在這里插入圖片描述
然后再進行重啟,再去訪問http://ip:port,即可mock成功
在這里插入圖片描述 proxy:使用代理協議轉發請求并返回真實內容
/**
* 使用代理協議轉發請求并返回真實內容
*/
public class Demo_05_Proxy {
@Test
public void start() {
int port = 8021;
// 實例化wirmockServer對象
WireMockServer wireMockServer = new WireMockServer(
wireMockConfig()
// 配置端口號
.port(port)
// 配置全局response模板
.extensions(new ResponseTemplateTransformer(true),
// 新建一個response的處理器
new ResponseTransformer() {
// 對response里的文本進行替換
@Override
public Response transform(Request request, Response response, FileSource fileSource, Parameters parameters) {
return Response.Builder.like(response)
.body(response.getBodyAsString().replace("Other Utilities","My proxy Utilities"))
.build();
}
@Override
public String getName() {
return "proxy demo";
}
})
);
// 啟動mock服務
wireMockServer.start();
configureFor(port);
// 配置mock服務中的一個stub
stubFor(get(urlMatching("/.*"))
.willReturn(
aResponse()
// 設置代理
.proxiedFrom("https://httpbin.ceshiren.com")
));
System.out.println("http://localhost:" + port);
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wiremock復位
WireMock.reset();
// wiremock停止(不停止下一次就無法進行調用了)
wireMockServer.stop();
}
}
啟動以后,訪問ip:port,修改成功!
在這里插入圖片描述