一、关键概念
- 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/__admin/recorder/
-
在这个界面输入你想要录制的网址,然后点击录制选项
-
新开一个页面,输入http://ip:port,会自动路由到你输入的网址,这个时候你可以在页面进行一些操作
-
点击stop停止录制
这个时候你会在resources目录里面看到mapping文件,里面放着录制好的请求 -
我们可以修改里面的内容
然后再进行重启,再去访问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,修改成功!