1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/*!
# httptest

Provide convenient mechanism for testing http clients against a locally
running http server. The typical usage is as follows:

* Start a server
* Configure the server by adding expectations
* Test your http client by making requests to the server
* On Drop the server verifies all expectations were met.

## Example Test

```
# async fn foo() {
use httptest::{Server, Expectation, matchers::*, responders::*};
// Start a server running on a local ephemeral port.
let server = Server::run();
// Configure the server to expect a single GET /foo request and respond
// with a 200 status code.
server.expect(
    Expectation::matching(request::method_path("GET", "/foo"))
    .respond_with(status_code(200)),
);

// The server provides server.addr() that returns the address of the
// locally running server, or more conveniently provides a server.url() method
// that gives a fully formed http url to the provided path.
let url = server.url("/foo");
let client = hyper::Client::new();
// Issue the GET /foo to the server.
let resp = client.get(url).await.unwrap();

// assert the response has a 200 status code.
assert!(resp.status().is_success());

// on Drop the server will assert all expectations have been met and will
// panic if not.
# }
```

# Server behavior

Typically the server is started by calling
[Server::run](struct.Server.html#method.run). It starts without any
expectations configured.

Expectations are added by calling
[Server::expect](struct.Server.html#method.expect). Every invocation of
expect appends a new expectation onto the list. Expectations are only removed
from the server on Drop or when
[Server::verify_and_clear](struct.Server.html#method.verify_and_clear) is
invoked. This guarantees that all expectations are always verified.

Expectations consist of:
* A matcher that determines which requests match this expectation
* The number of times a request matching this expectation is expected to be received
* A responder that indicates how the server should respond to the request.

When the server receives a request it iterates over all expectations in the
*reverse* order they have been added. When it reaches an expectation that
matches the request, it increments the hit count on that expectation and
verifies it has not exceeded it's expected number of requests. If the
limit has been exceeded a 500 error is returned, if the limit has not been
exceeded it uses the expectation's responder to respond to the request. If the
request does not match any expectation a 500 error is returned.

When the server is Dropped it:
* Stops running
* Panics if
  * any expectation did not receive the expected number of requests
  * a request was received that did not match any expectation

Clients can determine the address and port the server is reachable at using
[Server::addr](struct.Server.html#method.addr), or the helper methods
[Server::url](struct.Server.html#method.url) and
[Server::url_str](struct.Server.html#method.url_str).

## Server Pooling

Typical usage would use [Server::run](struct.Server.html#method.run) early in
each test case and have the Drop implementation at the end of the test assert
all expectations were met. This runs a separate server for each test. Rust's
test harness starts a separate thread for each test within a test-suite so
the machine running the test would likely end up running a server for each
#\[test\] function concurrently. For large test suites this could cause machine
wide resources (like tcp ports) to become scarce. To address this you could
use the --test-threads flag on the test-harness to limit the number of
threads running, or alternatively you could use a global
[ServerPool](struct.ServerPool.html) instance.

The [ServerPool](struct.ServerPool.html) allows limiting the number of
servers that can be running concurrently while still allowing test cases to
function independently.

### ServerPool example

```
# use httptest::ServerPool;
// Create a server pool that will create at most 2 servers.
static SERVER_POOL: ServerPool = ServerPool::new(2);

#[test]
fn test1() {
    let server = SERVER_POOL.get_server();
    server.expect(Expectation::matching(any()).respond_with(status_code(200)));

    // Send requests to server
    // Server will assert expectations on drop.
}

#[test]
fn test2() {
    let server = SERVER_POOL.get_server();
    server.expect(Expectation::matching(any()).respond_with(status_code(200)));

    // Send requests to server
    // Server will assert expectations on drop.
}

#[test]
fn test3() {
    let server = SERVER_POOL.get_server();
    server.expect(Expectation::matching(any()).respond_with(status_code(200)));

    // Send requests to server
    // Server will assert expectations on drop.
}
```

This is almost identical to tests without pooling, the only addition is
creating a static ServerPool instance, and using `SERVER_POOL.get_server()`
instead of `Server::run()`. This will effectively limit the amount of
concurrency of the test suite to two tests at a time. The first two tests to execute
`get_server()` will be handed servers without blocking, the 3rd test will block
in `get_server()` until one of the first 2 tests complete.

# Defining Expecations

Every expecation defines a request matcher, a defintion of the number of
times it's expected to be called, and what it should respond with.

### Expectation example

```
use httptest::{Expectation, matchers::*, responders::*};

// Define an Expectation that matches any request to path /foo, expects to
// receive at least 1 such request, and responds with a 200 response.
Expectation::matching(request::path("/foo"))
    .times(1..)
    .respond_with(status_code(200));
```

## Request Matchers

Defining which request an expecation matches is done in a composable manner
using a [Matcher](matchers/trait.Matcher.html) trait. The `Matcher` trait is
generic over an input type and defines a single method `matches` that returns
a boolean if the input matches.

A request matcher is any `Matcher` that accepts a
`http::Request<hyper::body::Bytes>` as input. A true result indicates the
request matches.

With that understanding we can discuss how to easily define a request
matcher. There are a variety of pre-defined matchers within the
[matchers](matchers/index.html) module. These matchers can be composed
together to define the values you want to match. The matchers fall into two
categories. Some of the matchers extract a value from the input type and pass
it to another matcher, other matchers accept an input type and return a bool.
These primitives provide an easy and flexible way to define custom logic.

### Matcher examples

```
// pull all the predefined matchers into our namespace.
use httptest::matchers::*;

// &str, String, and &[u8] all implement matchers that test for equality.
// All of these matchers return true when the input equals "/foo"
let mut m = eq("/foo");
let mut m = "/foo";
let mut m = "/foo".to_string();
let mut m = &b"/foo"[..];

// A mapper that returns true when the input matches the regex "(foo|bar).*"
let mut m = matches("(foo|bar).*");

// A request matcher that matches a request to path "/foo"
let mut m = request::path("/foo");

// A request matcher that matches a POST request
let mut m = request::method("POST");

// A request matcher that matches a POST with a path that matches the regex 'foo.*'
let mut m = all_of![
    request::method("POST"),
    request::path(matches("foo.*")),
];

# // Allow type inference to determine the request type.
# ExecutionContext::evaluate(&mut m, &http::Request::get("/").body("").unwrap());
```

## Times

Each expectation defines how many times a matching request is expected to
be received. The default is exactly once. The ExpectationBuilder provides a
[times](struct.ExpectationBuilder.html#method.times) method to specify the
number of requests expected.

```
# use httptest::{Expectation, matchers::any, responders::status_code};
// Expect exactly one request
Expectation::matching(any())
    .respond_with(status_code(200));

// Expect exactly two requests
Expectation::matching(any())
    .times(2)
    .respond_with(status_code(200));

// Expect at least 2 requests
Expectation::matching(any())
    .times(2..)
    .respond_with(status_code(200));

// Expect at most 2 requests
Expectation::matching(any())
    .times(..2)
    .respond_with(status_code(200));

// Expect between 2 and 5 requests
Expectation::matching(any())
    .times(2..6)
    .respond_with(status_code(200));

// Expect between 2 and 5 requests
Expectation::matching(any())
    .times(2..=5)
    .respond_with(status_code(200));

// Expect any number of requests.
Expectation::matching(any())
    .times(..)
    .respond_with(status_code(200));
```

The server will respond to any requests that violate the times restriction with
a 500 status code and the server will subsequently panic on Drop.

## Responder

Responders define how the server will respond to a matched request. There
are a number of implemented responders within the responders module. In
addition to the predefined responders you can provide any
`http::Response` with a body that can be cloned or implement your own
Responder.

### Responder example

```
use httptest::responders::*;

// respond with a successful 200 status code.
status_code(200);

// respond with a 404 page not found and a custom header.
status_code(404).append_header("X-My-Hdr", "my hdr val");

// respond with a successful 200 status code and body.
status_code(200).body("my body");

// respond with a json encoded body and custom header.
json_encoded(serde_json::json!({
    "my_key": 100,
    "my_key2": [1, 2, "foo", 99],
})).append_header("X-My-Hdr", "my hdr val");

// respond with a url encoded body (foo=bar&baz=bat)
url_encoded(&[
    ("foo", "bar"),
    ("baz", "bat")
]);

// alternate between responding with a 200 and a 404.
cycle![
    status_code(200),
    status_code(404),
];
```
!*/

#![deny(missing_docs)]

/// true if all the provided matchers return true.
///
/// The macro exists to conveniently box a list of matchers and put them into a
/// `Vec<Box<dyn Matcher>>`. The translation is:
///
/// `all_of![a, b] => all_of(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! all_of {
    ($($x:expr),*) => ($crate::matchers::all_of($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::all_of![$($x),*]);
}

/// true if any of the provided matchers return true.
///
/// The macro exists to conveniently box a list of matchers and put them into a
/// `Vec<Box<dyn Matcher>>`. The translation is:
///
/// `any_of![a, b] => any_of(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! any_of {
    ($($x:expr),*) => ($crate::matchers::any_of($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::any_of![$($x),*]);
}

/// a Responder that cycles through a list of responses.
///
/// The macro exists to conveniently box a list of responders and put them into a
/// `Vec<Box<dyn Responder>>`. The translation is:
///
/// `cycle![a, b] => cycle(vec![Box::new(a), Box::new(b)])`
#[macro_export]
macro_rules! cycle {
    ($($x:expr),*) => ($crate::responders::cycle($crate::vec_of_boxes![$($x),*]));
    ($($x:expr,)*) => ($crate::cycle![$($x),*]);
}

// hidden from docs because it's an implementation detail of the above macros.
#[doc(hidden)]
#[macro_export]
macro_rules! vec_of_boxes {
    ($($x:expr),*) => (std::vec![$(std::boxed::Box::new($x)),*]);
    ($($x:expr,)*) => ($crate::vec_of_boxes![$($x),*]);
}

mod into_times;
pub mod matchers;
pub mod responders;
mod server;
mod server_pool;

pub use server::{Expectation, ExpectationBuilder, Server};
pub use server_pool::{ServerHandle, ServerPool};