std.net.http.test.Mock
type pub inline MockA type for defining an expected request and its response.
Mocks are defined using methods such as Server.get and Server.post. A
Mock is only activated if Mock.then is called.
Mocks can define requirements such as expected query string parameters, headers, and how many times the request is expected to be received.
Query string matching
When a mock defines a set of expected query string parameters, these parameters are treated as the minimum required parameters, meaning additional parameters are allowed. The order of the request parameters must match that of the mock, and the values assigned must match exactly:
| Mock value | Request value | Result |
|---|---|---|
name=Alice | name=Alice&age=42 | match |
name=Alice&name=Bob | name=Alice | no match |
name=Alice&name=Bob | name=Bob&name=Alice | no match |
Header matching
Header matching follows the same rules as query string matching:
| Mock value | Request value | Result |
|---|---|---|
Accept: foo | Accept: foo | match |
Accept: foo, bar | Accept: foo | no match |
Accept: foo, bar | Accept: bar, foo | no match |
Body matching
Request bodies must match exactly that of the mock. If a mock doesn't define a body, a request only matches if it too doesn't specify a body.
Ordering
Mocks are matched against requests in order, skipping over mocks that are disabled (e.g. they're received the maximum number of requests allowed). For example:
import std.net.http.client (Client)
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
import std.uri (Uri)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let server = Server.new(t, fn (srv) {
srv.get('/').then(fn { Response.new.string('hello') })
srv.get('/').then(fn { Response.new.string('world') })
})
let client = Client.new
server.prepare_client(client)
let body = ByteArray.new
2.times(fn (_) {
let resp = client
.get(Uri.parse('http://example.com').or_panic)
.send
.or_panic
resp.body.read_all(body).or_panic
})
t.equal(body.to_string, 'helloworld')
})
tests.run
}
}
For the first request the response body is "hello", while for the second response it's "world", resulting in the buffer being equal to "helloworld".
Instance methods
at_least
Show source codeHide source code
fn pub move at_least(amount: Int) -> Self {
@calls = Calls.Minimum(amount)
self
}fn pub move at_least(amount: Int) -> MockSets the expected minimum number of requests to the given value.
Once the given number of requests is received this mock remains active.
Examples
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').at_least(2).then(fn { Response.new.string('hello') })
})
})
tests.run
}
}
at_most
Show source codeHide source code
fn pub move at_most(amount: Int) -> Self {
@calls = Calls.Maximum(amount)
self
}fn pub move at_most(amount: Int) -> MockSets the expected maximum number of requests to the given value.
Once the given number of requests is received, the mock is disabled.
Examples
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').at_most(2).then(fn { Response.new.string('hello') })
})
})
tests.run
}
}
bytes
Show source codeHide source code
fn pub move bytes(body: ref ByteArray) -> Self {
@body = recover Body.Bytes(body.clone)
self
}fn pub move bytes(body: ref ByteArray) -> MockSets the expected body to the given ByteArray.
Examples
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').bytes('hello'.to_byte_array).then(fn {
Response.new.string('hello')
})
})
})
tests.run
}
}
exactly
Show source codeHide source code
fn pub move exactly(amount: Int) -> Self {
@calls = Calls.Exactly(amount)
self
}fn pub move exactly(amount: Int) -> MockSets the expected exact number of requests to the given value.
Once the given number of requests is received, the mock is disabled.
Examples
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').exactly(2).then(fn { Response.new.string('hello') })
})
})
tests.run
}
}
header
Show source codeHide source code
fn pub move header(header: ref Header, value: String) -> Self {
@headers.add(recover header.clone, value)
self
}fn pub move header(header: ref Header, value: String) -> MockAdds a header and its value to the set of expected headers.
If the header is already assigned a value, the value is appended to the list of expected values.
Examples
This defines a mock that only matches if the request includes at least the
Accept: foo and User-Agent: bar headers:
import std.net.http (Header)
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv
.get('/')
.header(Header.accept, 'foo')
.header(Header.user_agent, 'bar')
.then(fn { Response.new.string('hello') })
})
})
tests.run
}
}
multipart_form
Show source codeHide source code
fn pub move multipart_form(
body: fn (mut multipart.Form[mut ByteArray, IoError]),
) -> Self {
let (header, buf) = multipart_form_data(body)
header(Header.content_type, header).bytes(buf)
}fn pub move multipart_form(body: fn (mut Form[mut ByteArray, Error])) -> MockExpects the request to be a multipart form request.
The body argument is a closure used to set the expected form fields and
their values.
The returned mock expects the Content-Type header to be set to
multipart/form-data; boundary=XXX where XXX is randomly generated string
using a fixed seed. To achieve this, the Client.random field of a
std.net.http.client.Client must be set to the result of
std.rand.Random.new(0). The easiest way of doing so is by using
RunningServer.prepare_client.
The body must be exactly the same as the form populated by the body
argument. If additional fields are provided or the order doesn't match, the
mock won't match a request.
Examples
import std.net.http.client (Client)
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
import std.uri (Uri)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let server = Server.new(t, fn (srv) {
srv
.post('/')
.multipart_form(fn (f) {
let _ = f.field('name').text('Alice')
let _ = f.field('age').text('42')
})
.then(fn { Response.new.string('created') })
})
let client = Client.new
server.prepare_client(client)
let body = ByteArray.new
let form = client.post(Uri.parse('/').or_panic).multipart_form.or_panic
form.add('name').text('Alice').or_panic
form.add('age').text('42').or_panic
let resp = form.send.or_panic
let _ = resp.body.read_all(body).or_panic
t.equal(body.to_string, 'created')
})
tests.run
}
}
query
Show source codeHide source code
fn pub move query(key: String, value: String) -> Self {
@query.add(key, value)
self
}fn pub move query(key: String, value: String) -> MockAdds a query string parameter and its value to the set of expected parameters.
If the parameter is already assigned a value, the value is appended to the list of expected values.
Examples
This defines a mock that only matches if the request includes at least the
name=Alice and age=42 query string parameters:
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').query('name', 'Alice').query('age', '42').then(fn {
Response.new.string('hello')
})
})
})
tests.run
}
}
string
Show source codeHide source code
fn pub move string(body: String) -> Self {
@body = recover Body.String(body)
self
}fn pub move string(body: String) -> MockSets the expected body to the given String.
Examples
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
srv.get('/').string('hello').then(fn { Response.new.string('hello') })
})
})
tests.run
}
}
then
Show source codeHide source code
fn pub move then(response: uni fn -> Response) {
let id = @server.next_id
let mat = recover {
Matcher(
id: id,
enabled: true,
calls: @calls,
query: @query,
headers: @headers,
body: @body,
)
}
@server.locations.push(@location)
@server.matchers.add(@method, @path, mat)
@server.responses.set(id, response)
}fn pub move then(response: uni fn -> Response)Installs the mock and defines its response body.
The response argument is the closure used for generating the response
body. This closure must be a uni closure such that it can be moved
between processes. This means the closure can only capture data that is
sendable.
Examples
In this example a uni Array[Int] is captured and modified for each
request, with the response showing the size of the array:
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let _server = Server.new(t, fn (srv) {
let mut counts = recover []
srv.get('/').then(fn move {
counts.push(1)
Response.new.string('size: ${counts.size}')
})
})
})
tests.run
}
}
url_encoded_form
Show source codeHide source code
fn pub move url_encoded_form(body: fn (mut Values)) -> Self {
header(Header.content_type, URL_FORM).string(url_encoded_form_data(body))
}fn pub move url_encoded_form(body: fn (mut Values)) -> MockExpect the request to be a URL encoded form request.
The body argument is a closure used to set the expected form fields and
their values.
The returned mock expects the Content-Type header to be set to
application/x-www-form-urlencoded and the body to be exactly the same as
the form populated by the body argument. If additional fields are provided
or the order doesn't match, the mock won't match a request.
Examples
import std.net.http.client (Client)
import std.net.http.server (Response)
import std.net.http.test (Server)
import std.test (Tests)
import std.uri (Uri)
type async Main {
fn async main {
let tests = Tests.new
tests.test('Example test', fn (t) {
let server = Server.new(t, fn (srv) {
srv
.post('/')
.url_encoded_form(fn (f) {
f.add('name', 'Alice')
f.add('age', '42')
})
.then(fn { Response.new.string('created') })
})
let client = Client.new
server.prepare_client(client)
let body = ByteArray.new
let form = client.post(Uri.parse('/').or_panic).url_encoded_form
form.add('name', 'Alice')
form.add('age', '42')
let resp = form.send.or_panic
let _ = resp.body.read_all(body).or_panic
t.equal(body.to_string, 'created')
})
tests.run
}
}