Moco mainly focuses on server configuration. There are only two kinds of API right now: Request and Response.
That means if we get the expected request and then return our response. Now, you can see a Moco reference in details.
WARNING the json configuration below is just a snippet for one pair of request and response, instead of the whole configuration file.
- Composite Java API Design
- Description as comment
- Request
- Response
- Content
- Status Code
- Version
- Header
- Proxy
- Redirect
- Cookie
- CORS
- Default All CORS
- CORS with allowOrigin/Access-Control-Allow-Origin
- CORS with allowMethods/Access-Control-Allow-Methods
- CORS with allowHeaders/Access-Control-Allow-Headers
- CORS with maxAge/Access-Control-Max-Age
- CORS with allowCredentials/Access-Control-Allow-Credentials
- CORS with exposeHeaders/Access-Control-Expose-Headers
- Attachment
- Latency
- Sequence
- Cycle
- JSON Response
- Mount
- Template
- Record and Replay
- Event
- Verify
- Miscellaneous
Moco Java API is designed in functional fashion which means you can composite any request or response easily.
server.request(and(by(uri("/target")), by(version(VERSION_1_0)))).response(with(text("foo")), header("Content-Type", "text/html"));
@Since 0.7
In all JSON APIs, you can use description to describe what is this session for. It's just used as comment, which will be ignored in runtime.
[
{
"description": "any response",
"response": {
"text": "foo"
}
}
]
@Since 0.7
If you want to response according to request content, Moco server can be configured as following:
- Java API
server.request(by("foo")).response("bar");
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"text" : "bar"
}
}
If request content is too large, you can put it in a file:
- Java API
server.request(by(file("foo.request"))).response("bar");
- JSON
{
"request" :
{
"file" : "foo.request"
},
"response" :
{
"text" : "bar"
}
}
@Since 1.2.0
You can also match binary request directly.
server.request(binary(new byte[] {1, 2, 3})).response("bar");
InputStream is also supported in binary
.
server.request(binary(new ByteArrayInputStream(new byte[]{1, 2, 3}))).response("bar");
@Since 0.7
If request uri is your major focus, Moco server could be like this:
- Java API
server.request(by(uri("/foo"))).response("bar");
- JSON
{
"request" :
{
"uri" : "/foo"
},
"response" :
{
"text" : "bar"
}
}
@Since 0.7
Sometimes, your request has parameters in query:
- Java API
server.request(and(by(uri("/foo")), eq(query("param"), "blah"))).response("bar");
- JSON
{
"request" :
{
"uri" : "/foo",
"queries" :
{
"param" : "blah"
}
},
"response" :
{
"text" : "bar"
}
}
@Since 0.7
It's easy to response based on specified HTTP method:
- Java API
server.get(by(uri("/foo"))).response("bar");
- JSON
{
"request" :
{
"method" : "get",
"uri" : "/foo"
},
"response" :
{
"text" : "bar"
}
}
Also for POST method:
- Java API
server.post(by("foo")).response("bar");
- JSON
{
"request" :
{
"method" : "post",
"text" : "foo"
},
"response" :
{
"text" : "bar"
}
}
and PUT method:
- Java API
server.put(by("foo")).response("bar");
- JSON
{
"request" :
{
"method" : "put",
"text" : "foo"
},
"response" :
{
"text" : "bar"
}
}
and DELETE method:
- Java API
server.delete(by(uri("/foo"))).response(status(200));
- JSON
{
"request" :
{
"method" : "delete",
"uri" : "/foo"
},
"response" :
{
"status" : "200"
}
}
If you do need other method, feel free to specify method directly:
- Java API
server.request(by(method("HEAD"))).response("bar");
- JSON
{
"request" :
{
"method" : "HEAD"
},
"response" :
{
"text" : "bar"
}
}
@Since 0.7
We can return different response for different HTTP version:
- Java API
server.request(by(version("HTTP/1.0"))).response("version");
- JSON
{
"request":
{
"version": "HTTP/1.0"
},
"response":
{
"text": "version"
}
}
@Since 0.7
We will focus HTTP header at times:
- Java API
server.request(eq(header("foo"), "bar")).response("blah")
- JSON
{
"request" :
{
"method" : "post",
"headers" :
{
"content-type" : "application/json"
}
},
"response" :
{
"text" : "bar"
}
}
@Since 0.7
Cookie is widely used in web development.
- Java API
server.request(eq(cookie("loggedIn"), "true")).response(status(200));
- JSON
{
"request" :
{
"uri" : "/cookie",
"cookies" :
{
"login" : "true"
}
},
"response" :
{
"text" : "success"
}
}
@Since 0.7
In web development, form is often used to submit information to server side.
- Java API
server.post(eq(form("name"), "foo")).response("bar");
- JSON
{
"request" :
{
"method" : "post",
"forms" :
{
"name" : "foo"
}
},
"response" :
{
"text" : "bar"
}
}
@Since 0.7
XML is a popular format for Web Services. When a request is in XML, only the XML structure is important in most cases and whitespace can be ignored. The xml
operator can be used for this case.
- Java API
server.request(xml(text("<request><parameters><id>1</id></parameters></request>"))).response("foo");
- JSON
{
"request":
{
"uri": "/xml",
"text":
{
"xml": "<request><parameters><id>1</id></parameters></request>"
}
},
"response":
{
"text": "foo"
}
}
NOTE: Please escape the quote in text.
The large request can be put into a file:
{
"request":
{
"uri": "/xml",
"file":
{
"xml": "your_file.xml"
}
},
"response":
{
"text": "foo"
}
}
@Since 0.7
For the XML/HTML request, Moco allows us to match request with XPath.
- Java API
server.request(eq(xpath("/request/parameters/id/text()"), "1")).response("bar");
- JSON
{
"request" :
{
"method" : "post",
"xpaths" :
{
"/request/parameters/id/text()" : "1"
}
},
"response" :
{
"text" : "bar"
}
}
@Since 1.3.0
Moco also allows you to match an XML request only for same struct no matter what actual content is.
- Java API
server.request(struct(xml("<foo>1</foo>")).response("response_for_xml_struct_request");
- JSON
{
"request":
{
"struct":
{
"xml" : "<foo>1</foo>"
}
},
"response":
{
"text": "response_for_xml_struct_request"
}
}
@Since 0.7
Json is rising with RESTful style architecture. Just like XML, in the most case, only JSON structure is important, so json
operator can be used.
- Java API
server.request(json(text("{\"foo\":\"bar\"}"))).response("foo");
@Since 0.12.0
json
will return a resource from 1.3.0.
server.request(by(json(text("{\"foo\":\"bar\"}")))).response("foo");
Note that this functionality is implemented in Jackson, please make sure your POJO is written in Jackson acceptable format.
- JSON
{
"request":
{
"uri": "/json",
"text":
{
"json": "{\"foo\":\"bar\"}"
}
},
"response":
{
"text": "foo"
}
}
NOTE: Please escape the quote in text.
If the response is JSON, we don't need to write JSON text with escape character in code.
@Since 0.10.2
You can give a POJO to Java API, it will be converted JSON text.
server.request(json(pojo)).response("foo");
@Since 0.12.0
json
will return a resource from 1.3.0.
server.request(by(json(pojo))).response("foo");
@Since 0.9.2
As you have seen, it is so boring to write json with escape character, especially in json configuration. So you can try the json shortcut. The upper case could be rewritten as following:
- JSON
{
"request": {
"uri": "/json",
"json": {
"foo": "bar"
}
},
"response": {
"text": "foo"
}
}
@Since 0.7
The large request can be put into a file:
- Java API
server.request(json(file("your_file.json"))).response("foo");
- JSON
{
"request":
{
"uri": "/json",
"file":
{
"json": "your_file.json"
}
},
"response":
{
"text": "foo"
}
}
@Since 0.7
For the JSON request, Moco allows us to match request with JSONPath.
- Java API
server.request(eq(jsonPath("$.book[*].price"), "1")).response("response_for_json_path_request");
- JSON
{
"request":
{
"json_paths":
{
"$.book[*].price": "1"
}
},
"response":
{
"text": "response_for_json_path_request"
}
}
@Since 1.3.0
Moco also allows you to match a JSON request only for same struct no matter what actual content is.
- Java API
server.request(struct(json("{\"foo\":1}")).response("response_for_json_struct_request");
- JSON
{
"request":
{
"struct":
{
"json" : {
"foo" :1
}
}
},
"response":
{
"text": "response_for_json_struct_request"
}
}
Moco also supports some operators which helps you write your expectation easily.
@Since 0.7
You may want to match your request with regular expression, match could be your helper:
- Java API
server.request(match(uri("/\\w*/foo"))).response("bar");
- JSON
{
"request":
{
"uri":
{
"match": "/\\w*/foo"
}
},
"response":
{
"text": "bar"
}
}
Moco is implemented by Java regular expression, you can refer here for more details.
@Since 0.9.2
starsWith operator can help you decide if the request information starts with a piece of text.
- Java API
server.request(startsWith(uri("/foo"))).response("bar");
- JSON
{
"request":
{
"uri":
{
"startsWith": "/foo"
}
},
"response":
{
"text": "bar"
}
}
@Since 0.9.2
endsWith operator can help you decide if the request information ends with a piece of text.
- Java API
server.request(endsWith(uri("foo"))).response("bar");
- JSON
{
"request":
{
"uri":
{
"endsWith": "foo"
}
},
"response":
{
"text": "bar"
}
}
@Since 0.9.2
contain operator helps you know whether the request information contains a piece of text.
- Java API
server.request(contain(uri("foo"))).response("bar");
- JSON
{
"request":
{
"uri":
{
"contain": "foo"
}
},
"response":
{
"text": "bar"
}
}
@Since 0.9.2
exist operator is used to decide whether the request information exists.
- Java API
server.request(exist(header("foo"))).response("bar");
- JSON
{
"request":
{
"headers": {
"foo": {
"exist" : "true"
}
}
},
"response":
{
"text": "bar"
}
}
For JSON API, you can decide whether the information does not exist.
- JSON
{
"request":
{
"headers": {
"foo": {
"exist" : "false"
}
}
},
"response":
{
"text": "bar"
}
}
@Since 1.4.0
path operator is provided to match uri with path variable.
- Java API
server.request(path(uri("/path/{path}/sub/{sub}"))).response("bar");
- JSON
{
"request":
{
"uri":
{
"path": "/path/{path}/sub/{sub}"
}
},
"response":
{
"text": "sub"
}
}
@Since 1.3.0
If you want to implement your own matcher, you can write with conditional
API which is supported in Java code.
server.request(conditional(request -> request.getContent().toString().equals("foo"))).response("foo");
@Since 0.7
As you have seen in previous example, response with content is pretty easy.
- Java API
server.request(by("foo")).response("bar");
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"text" : "bar"
}
}
The same as request, you can response with a file if content is too large to put in a string.
- Java API
server.request(by("foo")).response(file("bar.response"));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"file" : "bar.response"
}
}
@Since 0.10.1
You can specify file charset if you want to see it in correct encoding in console.
- Java API
server.response(file("src/test/resources/gbk.response", Charset.forName("GBK")));
- JSON
[
{
"response":
{
"file":
{
"name": "gbk.response",
"charset": "GBK"
}
}
}
]
Charset can also be used in path resource.
- Java API
server.response(pathResource("src/test/resources/gbk.response", Charset.forName("GBK")));
- JSON
[
{
"response":
{
"path_resource":
{
"name": "gbk.response",
"charset": "GBK"
}
}
}
]
@Since 1.2.0
You can also write binary response directly.
server.response(binary(new byte[] {1, 2, 3}));
@Since 1.2.0
If your response need to be transformed for some reason, e.g. encryption, you can transform
your content.
server.response(text("hello").transform(raw -> {
byte[] transformed = new byte[raw.length];
for (int i = 0; i < raw.length; i++) {
transformed[i] = (byte) (raw[i] + 1);
}
return transformed;
}));
@Since 1.2.0
If you need your own specific response content, you can write your own code with lambda.
server.response(text((request) -> "foo"));
server.response(binary((request) -> new byte[] {1, 2, 3}));
Currently, this function uses com.github.dreamhead.moco.Request
as argument which provide content only. If you need HTTP information in HTTP scenario, you can cast request to com.github.dreamhead.moco.HttpRequest
.
server.response(text((request) -> ((HttpRequest)request).getUri()));
@Since 0.7
Moco also supports HTTP status code response.
- Java API
server.request(by("foo")).response(status(200));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"status" : 200
}
}
@Since 0.7
By default, response HTTP version is supposed to request HTTP version, but you can set your own HTTP version:
- Java API
server.response(version(HttpProtocolVersion.VERSION_1_0));
- JSON
{
"request":
{
"uri": "/version10"
},
"response":
{
"version": "HTTP/1.0"
}
}
@Since 0.7
We can also specify HTTP header in response.
- Java API
server.request(by("foo")).response(header("content-type", "application/json"));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"headers" :
{
"content-type" : "application/json"
}
}
}
@Since 0.8
We can also response with the specified url, just like a proxy.
- Java API
server.request(by("foo")).response(proxy("http://www.github.com"));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"proxy" : "http://www.github.com"
}
}
Actually, proxy is more powerful than that. It can forward the whole request to the target url, including HTTP method, version, header, content etc.
@Since 0.7
Besides the basic functionality, proxy also support failover, which means if remote server is not available temporarily, the server will know recovery from local configuration.
- Java API
server.request(by("foo")).response(proxy("http://www.github.com", failover("failover.json")));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"proxy" :
{
"url" : "http://localhost:12306/unknown",
"failover" : "failover.json"
}
}
}
Proxy will save request/response pair into your failover file. If the proxy target is not reachable, proxy will failover from the file. This feature is very useful for development environment, especially for the case the integration server is not stable.
As the file suffix suggests, this failover file is actually a JSON file, which means we can read/edit it to return whatever we want.
@Since 0.9.1
Moco also supports playback which also save remote request and response into local file. The difference between failover and playback is that playback only accesses remote server when local request and response are not available.
- Java API
server.request(by("foo")).response(proxy("http://www.github.com", playback("playback.json")));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"proxy" :
{
"url" : "http://localhost:12306/unknown",
"playback" : "playback.json"
}
}
}
@Since 1.0.0
You can customize what remote statuses means that remote server is not available.
- Java API
server.request(by("foo")).response(proxy("http://www.github.com", failover("failover.json", 400, 500)));
- JSON
{
"request" :
{
"text": "foo"
},
"response" :
{
"proxy" :
{
"url" : "http://www.github.com",
"failover" : {
"file": "failover.json",
"status": [404, 500]
}
}
}
}
@Since 0.9.1
If we want to proxy with a batch of URLs in the same context, proxy can also help us.
- Java API
server.get(match(uri("/proxy/.*"))).response(proxy(from("/proxy").to("http://localhost:12306/target")));
- JSON
{
"request" :
{
"uri" : {
"match" : "/proxy/.*"
}
},
"response" :
{
"proxy" : {
"from" : "/proxy",
"to" : "http://localhost:12306/target"
}
}
}
Same with single url, you can also specify a failover.
- Java API
server.request(match(uri("/proxy/.*")))
.response(proxy("http://localhost:12306/unknown"), failover("failover.response")));
- JSON
{
"request" :
{
"uri" : {
"match" : "/failover/.*"
}
},
"response" :
{
"proxy" :
{
"from" : "/failover",
"to" : "http://localhost:12306/unknown",
"failover" : "failover.response"
}
}
}
and playback.
- Java API
server.request(match(uri("/proxy/.*")))
.response(proxy("http://localhost:12306/unknown"), playback("playback.response")));
- JSON
{
"request" :
{
"uri" : {
"match" : "/failover/.*"
}
},
"response" :
{
"proxy" :
{
"from" : "/failover",
"to" : "http://localhost:12306/unknown",
"playback" : "playback.response"
}
}
}
As you may find, we often set request match same context with response, so Moco gives us a shortcut to do that.
- Java API
server.proxy(from("/proxy").to("http://localhost:12306/target"));
- JSON
{
"proxy" : {
"from" : "/proxy",
"to" : "http://localhost:12306/target"
}
}
Same with failover
-
Java API
server.proxy(from("/proxy").to("http://localhost:12306/unknown"), failover("failover.response"));
-
JSON
{ "proxy" : { "from" : "/failover", "to" : "http://localhost:12306/unknown", "failover" : "failover.response" } }
and playback
-
Java API
server.proxy(from("/proxy").to("http://localhost:12306/unknown"), playback("playback.response"));
-
JSON
{ "proxy" : { "from" : "/failover", "to" : "http://localhost:12306/unknown", "playback" : "playback.response" } }
@Since 0.7
Redirect is a common case for normal web development. We can simply redirect a request to different url.
- Java API
server.get(by(uri("/redirect"))).redirectTo("http://www.github.com");
- JSON
{
"request" :
{
"uri" : "/redirect"
},
"redirectTo" : "http://www.github.com"
}
@Since 0.7
Cookie can also be in the response.
- Java API
server.response(cookie("loggedIn", "true"), status(302));
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : "true"
}
}
}
Cookie attributes are sent in http response, which are used by browsers to determine when to delete a cookie, block a cookie or whether to send a cookie to the server.
@Since 0.11.1
Path cookie attribute defines the scope of the cookie. You can add your own path
cookie attribute to your response.
- Java
server.response(cookie("loggedIn", "true", path("/")), status(302));
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"path" : "/"
}
}
}
}
@Since 0.11.1
Domain cookie attribute defines the scope of the cookie. You can add your own domain
cookie attribute to your response.
- Java
server.response(cookie("loggedIn", "true", domain("github.com")), status(302));
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"domain" : "github.com"
}
}
}
}
@Since 0.11.1
A secure cookie can only be transmitted over an encrypted connection. You can add your own secure
cookie attribute to your response.
- Java
server.response(cookie("loggedIn", "true", secure()), status(302));
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"secure" : "true"
}
}
}
}
@Since 0.11.1
An http only cookie cannot be accessed by client-side APIs. You can add your own httpOnly
cookie attribute to your response.
- Java
server.response(cookie("loggedIn", "true", httpOnly()), status(302));
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"httpOnly" : "true"
}
}
}
}
@Since 0.11.1
The Max-Age attribute can be used to set the cookie's expiration as an interval of seconds in the future, relative to the time the browser received the cookie. You can add your own maxAge
cookie attribute to your response.
- Java
server.response(cookie("loggedIn", "true", maxAge(1, TimeUnit.HOURS)), status(302))
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"maxAge": {
"duration": 1,
"unit": "hour"
}
}
}
}
}
@Since 1.5.0
The SameSite attribute can be set to control whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF).
Only "Strict", "Lax" and "None" can be set as sameSite value.
- Java
server.response(cookie("loggedIn", "true", sameSite("Lax")), status(302))
- JSON
{
"request" :
{
"uri" : "/cookie"
},
"response" :
{
"cookies" :
{
"login" : {
"value" : "true",
"sameSite": "Lax"
}
}
}
}
@Sinace 1.5.0
Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) on a web page to be requested from another domain outside the domain from which the resource originated. You can add your own cors
to your response.
You can add default all CORS to your response with cors
operator without any arguments.
- Java API
server.response(cors());
- JSON API
{
"response" :
{
"cors" : true
}
}
You can allow CORS with specific origin with cors
operator with allowOrigin
argument, which provides Access-Control-Allow-Origin
header.
- Java API
server.response(cors(allowOrigin("https://www.github.com/")));
- JSON API
{
"response" :
{
"cors" :
{
"allowOrigin" : "https://www.github.com/"
}
}
}
If you allow any origin with *
in origin
.
- Java API
server.response(cors(origin("*")));
- JSON
{
"response" :
{
"cors" :
{
"allowOrigin" : "*"
}
}
}
In JSON API, you can also use Access-Control-Allow-Origin
directly.
- JSON
{
"response" :
{
"cors" :
{
"Access-Control-Allow-Origin" : "https://www.github.com"
}
}
}
You can allow CORS with specific methods with cors
operator with allowMethods
argument, which provides Access-Control-Allow-Methods
header.
- Java API
server.response(cors(allowMethods("GET", "PUT")));
server.response(cors(allowMethods(HttpMethod.GET, HttpMethod.PUT)));
- JSON
{
"response" :
{
"cors" :
{
"allowMethods" : ["GET", "PUT"],
}
}
}
In JSON API, you can also use Access-Control-Allow-Methods
directly.
- JSON
{
"response" :
{
"cors" :
{
"Access-Control-Allow-Methods" : ["GET", "PUT"]
}
}
}
You can allow CORS with specific headers with cors
operator with allowHeaders
argument, which provides Access-Control-Allow-Headers
header.
- Java API
server.response(cors(allowHeaders("X-Header", "Y-Header")));
- JSON
{
"response" :
{
"cors" :
{
"allowHeaders" : ["X-Header", "Y-Header"]
}
}
}
In JSON API, you can also use Access-Control-Allow-Methods
directly.
{
"response" :
{
"cors" :
{
"Access-Control-Allow-Headers" : ["X-Header", "Y-Header"]
}
}
}
You can allow CORS with specific max age with cors
operator with maxAge
argument, which provides Access-Control-Max-Age
header.
- Java API
server.response(cors(maxAge(1728000, TimeUnit.SECONDS)));
- JSON
{
"response" :
{
"cors" :
{
"maxAge": {
"duration": 1728000,
"unit": "second"
}
}
}
}
In JSON API, you can also use Access-Control-Max-Age
directly.
{
"response" :
{
"cors" :
{
"Access-Control-Max-Age" : {
"duration": 1728000,
"unit": "second"
}
}
}
}
You can allow CORS with specific credentials with cors
operator with allowCredentials
argument, which provides Access-Control-Allow-Credentials
header.
- Java API
server.response(cors(allowCredentials(true)));
- JSON
{
"response" :
{
"cors" :
{
"allowCredentials" : true
}
}
}
In JSON API, you can also use Access-Control-Allow-Credentials
directly.
{
"response" :
{
"cors" :
{
"Access-Control-Allow-Credentials" : true
}
}
}
You can allow CORS with specific expose headers with cors
operator with exposeHeaders
argument, which provides Access-Control-Expose-Headers
header.
- Java API
server.response(cors(exposeHeaders("X-Header", "Y-Header")));
- JSON
{
"response" :
{
"cors" :
{
"exposeHeaders" : ["X-Header", "Y-Header"]
}
}
}
In JSON API, you can also use Access-Control-Expose-Headers
directly.
{
"response" :
{
"cors" :
{
"Access-Control-Expose-Headers" : ["X-Header", "Y-Header"]
}
}
}
@Since 0.10.0
Attachment is often used in web development. You can setup an attachment in Moco as following. As you will see, you'd better set a filename for client to receive.
- Java API
server.get(by(uri("/"))).response(attachment("foo.txt", file("foo.response")));
- JSON
{
"request": {
"uri": "/file_attachment"
},
"response": {
"attachment": {
"filename": "foo.txt",
"file": "foo.response"
}
}
}
@Since 0.7
Sometimes, we need a latency to simulate slow server side operation.
@Since 0.10.1
It's easy to setup latency with time unit.
- Java API
server.response(latency(1, TimeUnit.SECONDS));
- JSON
{
"request" :
{
"text" : "foo"
},
"response" :
{
"latency":
{
"duration": 1,
"unit": "second"
}
}
}
The original API without time unit introduced in 0.7 has been deprecated.
@Since 0.7
Sometimes, we want to simulate a real-world operation which change server side resource. For example:
- First time you request a resource and "foo" is returned
- We update this resource
- Again request the same URL, updated content, e.g. "bar" is expected.
We can do that by
server.request(by(uri("/seq"))).response(seq("foo", "bar", "blah"));
The other response settings are able to be set as well.
server.request(by(uri("/seq"))).response(seq(status(302), status(302), status(200)));
@Since 0.12.0
{
"request" : {
"uri" : "/seq"
},
"response": {
"seq": [
{
"text" : "foo"
},
{
"text" : "bar"
},
{
"text" : "blah"
}
]
}
}
The other response settings are able to be set for json as well.
{
"request" : {
"uri" : "/seq"
},
"response": {
"seq": [
{
"status" : "302"
},
{
"status" : "302"
},
{
"status" : "200"
}
]
}
}
@Since 1.0.0
Cycle is similar to seq
, but it will return response as cycle. An example is as following:
server.request(by(uri("/cycle"))).response(cycle("foo", "bar", "blah"));
The response will returned as cycle:
- foo
- bar
- blah
- foo
- bar
- blah
- ...
The other response settings are able to be set as well.
server.request(by(uri("/cycle"))).response(cycle(status(302), status(302), status(200)));
@Since 0.12.0
{
"request" : {
"uri" : "/cycle"
},
"response": {
"cycle": [
{
"text" : "foo"
},
{
"text" : "bar"
},
{
"text" : "blah"
}
]
}
}
The other response settings are able to be set for json as well.
{
"request" : {
"uri" : "/cycle"
},
"response": {
"cycle": [
{
"status" : "302"
},
{
"status" : "302"
},
{
"status" : "200"
}
]
}
}
If the response is JSON, we don't need to write JSON text with escape character in code.
@Since 0.10.2
You can give a POJO to Java API, it will be converted JSON text. Hint, this api will setup Content-Type header as well.
server.request(by(uri("/json"))).response(toJson(pojo));
@Since 0.12.0
toJson
will be removed from 0.12.0, use json
instead.
server.request(by(uri("/json"))).response(json(pojo));
Note that this functionality is implemented in Jackson, please make sure your POJO is written in Jackson acceptable format.
@Since 0.9.2
For JSON API, just give json object directly
{
"request":
{
"uri": "/json"
},
"response":
{
"json":
{
"foo" : "bar"
}
}
}
@Since 1.2.0
For API user, if you want to return create dynamic JSON based on the request, you can use lambda to do this.
- With request
server.request(by(uri("/json"))).response(json((request) -> new Pojo()));
@Since 0.7
Moco allows us to mount a directory to uri.
- Java API
server.mount(dir, to("/uri"));
- JSON
{
"mount" :
{
"dir" : "dir",
"uri" : "/uri"
}
}
Glob is acceptable to filter specified files, e.g we can include by
- Java API
server.mount(dir, to("/uri"), include("*.txt"));
- JSON
{
"mount" :
{
"dir" : "dir",
"uri" : "/uri",
"includes" :
[
"*.txt"
]
}
}
or exclude by
- Java API
server.mount(dir, to("/uri"), exclude("*.txt"));
- JSON
{
"mount" :
{
"dir" : "dir",
"uri" : "/uri",
"excludes" :
[
"*.txt"
]
}
}
even compose them by
- Java API
server.mount(dir, to("/uri"), include("a.txt"), exclude("b.txt"), include("c.txt"));
- JSON
{
"mount" :
{
"dir" : "dir",
"uri" : "/uri",
"includes" :
[
"a.txt",
"c.txt"
],
"excludes" :
[
"b.txt"
]
}
}
@Since 0.10.1 You can also specify some response information like normal response, e.g.
- JSON
{
"mount" :
{
"dir" : "dir",
"uri" : "/uri",
"headers" : {
"Content-Type" : "text/plain"
}
}
}
Sometimes, we need to customize our response based on something, e.g. response should have same header with request.
The goal can be reached by template:
You can get request information with req
in template.
@Since 0.8
With req.version
, request version can be retrieved in template.
The following example will return response version as content.
- Java
server.request(by(uri("/template"))).response(template("${req.version}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.version}"
}
}
}
@Since 0.8
Request method is identified by req.method
.
- Java
server.request(by(uri("/template"))).response(template("${req.method}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.method}"
}
}
}
@Since 0.8
All request content can be used in template with req.content
- Java
server.request(by(uri("/template"))).response(template("${req.content}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.content}"
}
}
}
@Since 0.8
Header is another important element in template and we can use req.headers
for headers.
- Java
server.request(by(uri("/template"))).response(template("${req.headers['foo']}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.headers['foo']}"
}
}
}
@Since 0.8
req.queries
helps us to extract request query.
- Java
server.request(by(uri("/template"))).response(template("${req.queries['foo']}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.queries['foo']}"
}
}
}
@Since 0.9.1
req.forms
can extract form value from request.
- Java
server.request(by(uri("/template"))).response(template("${req.forms['foo']}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.forms['foo']}"
}
}
}
@Since 0.9.1
Cookie from request can extracted by req.cookies
.
- Java
server.request(by(uri("/template"))).response(template("${req.cookies['foo']}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.cookies['foo']}"
}
}
}
@Since 1.0.0
If your request is a JSON request, you can use req.json
to visit your json object.
Note that make sure your request is a JSON request, otherwise an exception will be thrown.
- Java
server.request(by(uri("/template"))).response(template("${req.json.foo}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.json.foo}"
}
}
}
@Since 1.4.0
If your request is an XML request, you can use req.xml
to visit your xml object.
Note that make sure your request is an XML request, otherwise an exception will be thrown.
- Java
server.request(by(uri("/template"))).response(template("${req.xml.foo}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.xml.foo}"
}
}
}
@Since 1.4.0
req.client.address
can be used in template to return client IP address.
server.request(by(uri("/template"))).response(template("${req.client.address}"));
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.client.address}"
}
}
}
@Since 1.5.0
req.client.port
can be used in template to return client port.
server.request(by(uri("/template"))).response(template("${req.client.port}"));
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${req.client.port}"
}
}
}
@Since 1.4.0
req.path
can work with path
API to extract path parameter as template variable.
server.request(path(uri("/path/{foo}"))).response(template("${req.path.foo}"));
{
"request": {
"uri": {
"path": "/path/{foo}"
}
},
"response": {
"text": {
"template": "${req.path.foo}"
}
}
}
@Since 0.9.1
You can provide your own variables in your template.
- Java
server.request(by(uri("/template"))).response(template("${foo}", "foo", "bar"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": {
"with" : "${foo}",
"vars" : {
"foo" : "bar"
}
}
}
}
}
@Since 0.10.0
You can also use extractor to extract information from request.
- Java
server.request(by(uri("/template"))).response(template("${foo}", "foo", jsonPath("$.book[*].price")));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": {
"with" : "${foo}",
"vars" : {
"foo" : {
"json_path": "$.book[*].price"
}
}
}
}
}
}
Other extractors, e.g. xpath also work here.
@Since 1.0.0
Current time can retrieved by 'now' function and a date format string should be passed as argument.
- Java
server.request(by(uri("/template"))).response(template("${now('yyyy-MM-dd')}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${now(\"yyyy-MM-dd\")}"
}
}
}
@Since 1.0.0
random
will generate a random number. If you didn't pass any argument, the generated random will be between 0 and 1.
- Java
server.request(by(uri("/template"))).response(template("${random()}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${random()}"
}
}
}
The first argument is random number range which means the generated number will be between 0 and range.
- Java
server.request(by(uri("/template"))).response(template("${random(100)}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${random(100)}"
}
}
}
@Since 1.3.0
If you want to limit your random number in a range. You can give two number as a start and an end.
- Java
server.request(by(uri("/template"))).response(template("${random(99, 100)}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${random(99, 100)}"
}
}
}
The last argument could be a number format.
- Java
server.request(by(uri("/template"))).response(template("${random(99, 100, '###.###')}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${random(99, 100, \"###.###\")}"
}
}
}
You can also use number format directly without range. By default, range is from 0 to 1.
- Java
server.request(by(uri("/template"))).response(template("${random('###.###')}"));
- JSON
{
"request": {
"uri": "/template"
},
"response": {
"text": {
"template": "${random(\"###.###\")}"
}
}
}
@Since 0.10.2
Redirect can also be set as template.
- Java
server.request(by(uri("/redirectTemplate"))).redirectTo(template("${var}", "var", ""https://github.com"));
- Json
{
"request" :
{
"uri" : "/redirect-with-template"
},
"redirectTo" : {
"template" : {
"with" : "${url}",
"vars" : {
"url" : "https://github.com"
}
}
}
}
@Since 0.10.1
Template can also be used in file name, thus response can be different based on different request.
- Java
server.response(file(template("${req.headers['foo'].txt")));
- JSON
[
{
"response": {
"file": {
"name": {
"template": "${req.headers['foo'].txt"}"
}
}
}
}
]
@Since 0.11.1
You can use template in proxy API, so that you can dynamically decide which URL you will forward the request to.
- Java
server.request(by(uri("/proxy"))).response(proxy(template("http://localhost:12306/${req.queries['foo']}")))
- JSON
{
"request" : {
"uri" : "/proxy"
},
"response" : {
"proxy" : {
"url" : {
"template": "http://localhost:12306/${req.queries['foo']}"
}
}
}
}
Template also can ben applied to event action. Check out Event for more details about event.
- Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://localhost:12306/target"), template("${target}", of("target", var("target"))))));
- JSON
{
"request": {
"uri": "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"post": {
"url": "http://localhost:12306/target",
"content": {
"template": {
"with": "${target}",
"vars": {
"target" : "target"
}
}
}
}
}
}
}
@Since 1.1.0
More powerful dynamic features are required even if you can implement some with a template. For instance, you may want to change one URL to return a different response. Record and replay will help.
In the following, /record
will be used to record request and /replay
will return the recorded request content. You can also configure record and replay for more capabilities.
In this case, group
will be used to distinguish different record sources.
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo")));
group
help you distinguish different record source, which means you can have same configuration in different group. If no group
is provided, default group will be applied.
- Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo")));
- JSON
The default parameter for record
and replay
is group.
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : "foo"
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : "foo"
}
}
]
You can also specify group
name explicitly.
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : {
"group": "foo"
}
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : {
"group": "foo"
}
}
}
]
In the same group, you can use identifier
to distinguish different requst. You can extract identifier from request with template syntax.
- Java
server.request(by(uri("/record"))).response(record(identifier("${req.queries['type']}")));
server.request(by(uri("/replay"))).response(replay(identifier("${req.queries['type']}")));
- JSON
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : {
"identifier": {
"template": "${req.queries['type']}"
}
}
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : {
"identifier": {
"template": "${req.queries['type']}"
}
}
}
}
]
In the above case, if you record your request with
- URI
/record?type=foo
and content isfoo
- URI
/record?type=bar
and content isbar
,
When you access URI /replay?type=foo
, foo
will be returned and access URI /replay?type=bar
, bar
will be returned.
The recorded content will be returned by default. But sometimes you hope return different content.
In the following case, with modifier
, type in request parameter will be returned as replay content. As you expect, modifier
only apply in replay. Template syntax will apply here.
- Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo"),
modifier("${req.queries['type']}")));
- JSON
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : {
"group": "foo",
}
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : {
"group": "foo",
"modifier": "${req.queries['type']}"
}
}
}
]
By default, only response content will be set with modifier. If you want more, you can setup the response with other response configuration.
- Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo"),
modifier(template("${req.content}"),
header("X-REPLAY", template("${req.queries['type']}"))
)
));
- JSON
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : {
"group": "foo",
}
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : {
"group": "foo",
"modifier": {
"text": {
"template": "${req.content}"
},
"header": {
"X-REPLAY": {
"template": "${req.queries['type']}"
}
}
}
}
}
}
]
If you want to reuse recorded request after restart, tape
will help you persist the recorded request just like a tape.
- Java
server.request(by(uri("/record"))).response(record(group("foo"), tape("/path/to/tape")));
server.request(by(uri("/foo-replay"))).response(replay(record(group("foo"),
tape("/path/to/tape")));
- JSON
[
{
"request" : {
"uri" : "/record"
},
"response" : {
"record" : {
"group": "foo",
"tape": "/path/to/tape"
}
}
},
{
"request" : {
"uri" : "/replay"
},
"response" : {
"record" : {
"group": "foo",
"tape": "/path/to/tape"
}
}
}
]
You may need to request another site when you receive a request, e.g. OAuth. Event could be your helper at that time.
@Since 0.9
Complete event will be fired after your request has been handled completely.
You can get
from an URL.
- Java
server.request(by(uri("/event"))).response("event").on(complete(get("http://another_site")));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"get" : {
"url" : "http://another_site"
}
}
}
}
@Since 1.0.0
And also get
with headers.
- Java
server.request(by(uri("/event"))).response("event").on(complete(get("http://another_site", header("foo", "bar"))));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"get" : {
"url" : "http://another_site",
"headers": {
"foo": "bar"
}
}
}
}
}
You can post some content as well.
- Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://another_site", "content")));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"post" : {
"url" : "http://another_site",
"content": "content"
}
}
}
}
@Since 0.12.0
If your post content is JSON, you can use json
in your configuration directly.
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"post" : {
"url" : "http://another_site",
"json": {
"foo" : "bar"
}
}
}
}
}
@Since 1.0.0
And also post
with headers.
- Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://another_site", "content", header("foo", "bar"))));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"post" : {
"url" : "http://another_site",
"content": "content",
"headers": {
"foo": "bar"
}
}
}
}
}
Let me know if you need more methods.
@Since 0.9
Synchronized request is used by default, which means response won't be returned to client until event handler is finished.
If it is not your expected behavior, you can changed with async API which will fire event asynchronously.
- Java
server.request(by(uri("/event"))).response("event").on(complete(async(post("http://another_site", "content"))));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"async" : "true",
"post" : {
"url" : "http://another_site",
"content": "content"
}
}
}
}
You can specify latency for this asynchronous request as well to wait a while.
- Java
server.request(by(uri("/event"))).response("event").on(complete(async(post("http://another_site", "content"), latency(1000))));
- JSON
{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"async" : "true",
"latency" : 1000,
"post" : {
"url" : "http://another_site",
"content": "content"
}
}
}
}
@Since 0.9
Someone may want to verify what kind of request has been sent to server in testing framework.
You can verify request like this:
RequestHit hit = requestHit();
final HttpServer server = httpServer(12306, hit);
server.get(by(uri("/foo"))).response("bar");
running(server, () -> {
assertThat(helper.get(remoteUrl("/foo")), is("bar"));
});
hit.verify(by(uri("/foo"))), times(1));
You can also verify unexpected request like this:
hit.verify(unexpected(), never());
Many verification can be used:
- never: none of this kind of request has been sent.
- once: only once this kind of request has been sent.
- time: how many times this kind of request has been sent.
- atLeast: at least how many time this kind of request has been sent.
- atMost: at most how many time this kind of request has been sent.
- between: the times this kind of request has been sent should be between min and max times.
@Since 0.9
If you specify a port for your stub server, it means the port must be available when you start server. This is not case sometimes.
Moco provides you another way to start your server: specify no port, and it will look up an available port. The port can be got by port() method. The example is as follow:
final HttpServer server = httpServer();
server.response("foo");
running(server, () -> {
Content content = Request.Get("http://localhost:" + server.port()).execute().returnContent();
assertThat(content.asString(), is("foo"));
});
The port will be returned only when server is started, otherwise the exception will be thrown.
For standalone server, if you need this behaviour, simply don't give port argument.
java -jar moco-runner-<version>-standalone.jar http -c foo.json
The port information will shown on screen.
@Since 0.9.1
If you want to know more about how your Moco server running, log will be your helper.
final HttpServer server = httpServer(log());
The Moco server will log all your requests and responses in your console.
It you want to keep log, you can use log interface as following:
final HttpServer server = httpServer(log("path/to/log.log"));
@Since 0.10.1
Log content may contain some non UTF-8 character, charset could be specified in log API:
final HttpServer server = httpServer(log("path/to/log.log", Charset.forName("GBK")));
The log will be saved in your log file.
Log will help you for some legacy system to know what detailed request/response looks like. You also need to do some verification work. Here is the case.
RequestHit hit = requestHit();
final HttpServer server = httpServer(port(), hit, log());