Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring the mock server (host, port, assert_on_drop) #191

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading