Skip to content

Commit

Permalink
Allow configuring the mock server (host, port, assert_on_drop)
Browse files Browse the repository at this point in the history
  • Loading branch information
lipanski committed Feb 21, 2024
1 parent 12cb5d0 commit 3e2d466
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ appveyor = { repository = "lipanski/mockito", branch = "master", service = "gith

[dependencies]
assert-json-diff = "2.0"
colored = { version = "2.0", optional = true }
colored = { version = "~2.0", optional = true }
futures = "0.3"
hyper = { version = "0.14", features = ["http1", "http2", "server", "stream"] }
log = "0.4"
Expand Down
20 changes: 19 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
//! In order to write async tests, you'll need to use the `_async` methods:
//!
//! - `Server::new_async`
//! - `Server::new_with_opts_async`
//! - `Mock::create_async`
//! - `Mock::assert_async`
//! - `Mock::matched_async`
Expand All @@ -162,6 +163,23 @@
//! This happens because a function attempted to block the current thread while the thread is being used to drive asynchronous tasks.
//! ```
//!
//! # Configuring the server
//!
//! When calling `Server::new()`, a mock server with default options is returned from the server
//! pool. This should suffice for most use cases.
//!
//! If you'd like to bypass the server pool or configure the server in a different
//! way, you can use `Server::new_with_opts`. The following **options** are available:
//!
//! - `host`: allows setting the host (defaults to `127.0.0.1`)
//! - `port`: allows setting the port (defaults to a randomly assigned free port)
//! - `assert_on_drop`: automatically call `Mock::assert()` before dropping a mock (defaults to `false`)
//!
//! ```
//! let opts = mockito::ServerOpts { assert_on_drop: true, ..Default::default() };
//! let server = mockito::Server::new_with_opts(opts);
//! ```
//!
//! # Matchers
//!
//! Mockito can match your request by method, path, query, headers or body.
Expand Down Expand Up @@ -674,7 +692,7 @@ pub use error::{Error, ErrorKind};
pub use matcher::Matcher;
pub use mock::{IntoHeaderName, Mock};
pub use request::Request;
pub use server::Server;
pub use server::{Server, ServerOpts};
pub use server_pool::ServerGuard;

mod diff;
Expand Down
17 changes: 16 additions & 1 deletion src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,16 @@ pub struct Mock {
inner: InnerMock,
/// Used to warn of mocks missing a `.create()` call. See issue #112
created: bool,
assert_on_drop: bool,
}

impl Mock {
pub(crate) fn new<P: Into<Matcher>>(state: Arc<RwLock<State>>, method: &str, path: P) -> Mock {
pub(crate) fn new<P: Into<Matcher>>(
state: Arc<RwLock<State>>,
method: &str,
path: P,
assert_on_drop: bool,
) -> Mock {
let inner = InnerMock {
id: thread_rng()
.sample_iter(&Alphanumeric)
Expand All @@ -166,6 +172,7 @@ impl Mock {
state,
inner,
created: false,
assert_on_drop,
}
}

Expand Down Expand Up @@ -356,6 +363,10 @@ impl Mock {
///
/// Sets the body of the mock response dynamically. The response will use chunked transfer encoding.
///
/// The callback function will be called only once. You can sleep in between calls to the
/// writer to simulate delays between the chunks. The callback function can also return an
/// error after any number of writes in order to abort the response.
///
/// The function must be thread-safe. If it's a closure, it can't be borrowing its context.
/// Use `move` closures and `Arc` to share any data.
///
Expand Down Expand Up @@ -661,6 +672,10 @@ impl Drop for Mock {
if !self.created {
log::warn!("Missing .create() call on mock {}", self);
}

if self.assert_on_drop {
self.assert();
}
}
}

Expand Down
118 changes: 100 additions & 18 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use crate::{Error, ErrorKind, Matcher, Mock};
use hyper::server::conn::Http;
use hyper::service::service_fn;
use hyper::{Body, Request as HyperRequest, Response, StatusCode};
use std::default::Default;
use std::fmt;
use std::net::SocketAddr;
use std::net::{IpAddr, SocketAddr};
use std::ops::Drop;
use std::str::FromStr;
use std::sync::{mpsc, Arc, RwLock};
use std::thread;
use tokio::net::TcpListener;
Expand Down Expand Up @@ -107,6 +109,43 @@ impl State {
}
}

///
/// Options to configure a mock server. Provides a default implementation.
///
/// ```
/// let opts = mockito::ServerOpts { port: 1234, ..Default::default() };
/// ```
///
pub struct ServerOpts {
/// The server host (defaults to 127.0.0.1)
pub host: &'static str,
/// The server port (defaults to a randomly assigned free port)
pub port: u16,
/// Automatically call `assert()` before dropping a mock (defaults to false)
pub assert_on_drop: bool,
}

impl ServerOpts {
pub(crate) fn address(&self) -> SocketAddr {
let ip = IpAddr::from_str(self.host).unwrap();
SocketAddr::from((ip, self.port))
}
}

impl Default for ServerOpts {
fn default() -> Self {
let host = "127.0.0.1";
let port = 0;
let assert_on_drop = false;

ServerOpts {
host,
port,
assert_on_drop,
}
}
}

///
/// One instance of the mock server.
///
Expand All @@ -121,16 +160,25 @@ impl State {
/// let mut server = mockito::Server::new();
/// ```
///
/// If for any reason you'd like to bypass the server pool, you can use `Server::new_with_port`:
/// If you'd like to bypass the server pool or configure the server in a different way
/// (by setting a custom host and port or enabling auto-asserts), you can use `Server::new_with_opts`:
///
/// ```
/// let mut server = mockito::Server::new_with_port(0);
/// let opts = mockito::ServerOpts { port: 0, ..Default::default() };
/// let server_with_port = mockito::Server::new_with_opts(opts);
///
/// let opts = mockito::ServerOpts { host: "0.0.0.0", ..Default::default() };
/// let server_with_host = mockito::Server::new_with_opts(opts);
///
/// let opts = mockito::ServerOpts { assert_on_drop: true, ..Default::default() };
/// let server_with_auto_assert = mockito::Server::new_with_opts(opts);
/// ```
///
#[derive(Debug)]
pub struct Server {
address: SocketAddr,
state: Arc<RwLock<State>>,
assert_on_drop: bool,
}

impl Server {
Expand Down Expand Up @@ -175,30 +223,55 @@ impl Server {
}

///
/// Starts a new server on a given port. If the port is set to `0`, a random available
/// port will be assigned. Note that **this call bypasses the server pool**.
/// **DEPRECATED:** Use `Server::new_with_opts` instead.
///
#[deprecated(since = "1.3.0", note = "Use `Server::new_with_opts` instead")]
#[track_caller]
pub fn new_with_port(port: u16) -> Server {
let opts = ServerOpts {
port,
..Default::default()
};
Server::try_new_with_opts(opts).unwrap()
}

///
/// Starts a new server with the given options. Note that **this call bypasses the server pool**.
///
/// This method will panic on failure.
///
#[track_caller]
pub fn new_with_port(port: u16) -> Server {
Server::try_new_with_port(port).unwrap()
pub fn new_with_opts(opts: ServerOpts) -> Server {
Server::try_new_with_opts(opts).unwrap()
}

///
/// Same as `Server::new_with_port` but async.
/// **DEPRECATED:** Use `Server::new_with_opts_async` instead.
///
#[deprecated(since = "1.3.0", note = "Use `Server::new_with_opts_async` instead")]
pub async fn new_with_port_async(port: u16) -> Server {
Server::try_new_with_port_async(port).await.unwrap()
let opts = ServerOpts {
port,
..Default::default()
};
Server::try_new_with_opts_async(opts).await.unwrap()
}

///
/// Same as `Server::new_with_opts` but async.
///
pub async fn new_with_opts_async(opts: ServerOpts) -> Server {
Server::try_new_with_opts_async(opts).await.unwrap()
}

///
/// Same as `Server::new_with_port` but won't panic on failure.
/// Same as `Server::new_with_opts` but won't panic on failure.
///
#[track_caller]
pub(crate) fn try_new_with_port(port: u16) -> Result<Server, Error> {
pub(crate) fn try_new_with_opts(opts: ServerOpts) -> Result<Server, Error> {
let state = Arc::new(RwLock::new(State::new()));
let address = SocketAddr::from(([127, 0, 0, 1], port));
let address = opts.address();
let assert_on_drop = opts.assert_on_drop;
let (address_sender, address_receiver) = mpsc::channel::<SocketAddr>();
let runtime = runtime::Builder::new_current_thread()
.enable_all()
Expand All @@ -215,17 +288,22 @@ impl Server {
.recv()
.map_err(|err| Error::new_with_context(ErrorKind::ServerFailure, err))?;

let server = Server { address, state };
let server = Server {
address,
state,
assert_on_drop,
};

Ok(server)
}

///
/// Same as `Server::try_new_with_port` but async.
/// Same as `Server::try_new_with_opts` but async.
///
pub(crate) async fn try_new_with_port_async(port: u16) -> Result<Server, Error> {
pub(crate) async fn try_new_with_opts_async(opts: ServerOpts) -> Result<Server, Error> {
let state = Arc::new(RwLock::new(State::new()));
let address = SocketAddr::from(([127, 0, 0, 1], port));
let address = opts.address();
let assert_on_drop = opts.assert_on_drop;
let (address_sender, address_receiver) = mpsc::channel::<SocketAddr>();
let runtime = runtime::Builder::new_current_thread()
.enable_all()
Expand All @@ -242,7 +320,11 @@ impl Server {
.recv()
.map_err(|err| Error::new_with_context(ErrorKind::ServerFailure, err))?;

let server = Server { address, state };
let server = Server {
address,
state,
assert_on_drop,
};

Ok(server)
}
Expand Down Expand Up @@ -296,7 +378,7 @@ impl Server {
/// ```
///
pub fn mock<P: Into<Matcher>>(&mut self, method: &str, path: P) -> Mock {
Mock::new(self.state.clone(), method, path)
Mock::new(self.state.clone(), method, path, self.assert_on_drop)
}

///
Expand Down
4 changes: 2 additions & 2 deletions src/server_pool.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Server;
use crate::{Error, ErrorKind};
use crate::{Server, ServerOpts};
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut, Drop};
use std::sync::Mutex;
Expand Down Expand Up @@ -75,7 +75,7 @@ impl ServerPool {
let recycled = self.free_list.lock().unwrap().pop_front();
let server = match recycled {
Some(server) => server,
None => Server::try_new_with_port_async(0).await?,
None => Server::try_new_with_opts_async(ServerOpts::default()).await?,
};

Ok(ServerGuard::new(server, permit))
Expand Down

0 comments on commit 3e2d466

Please sign in to comment.