diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..cc9d82e
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,6 @@
+[alias]
+xtask = "run --manifest-path ./xtask/Cargo.toml --"
+
+# Uncomment the lines below to allow your IDE to be in sim mode
+# [build]
+# rustflags = ["--cfg", "tokio_unstable", "--cfg", "sim"]
diff --git a/Cargo.lock b/Cargo.lock
index e910e5d..fad52d1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -104,9 +104,9 @@ checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
 
 [[package]]
 name = "bytes"
-version = "1.1.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
 
 [[package]]
 name = "cfg-if"
@@ -208,6 +208,7 @@ checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
 dependencies = [
  "futures-channel",
  "futures-core",
+ "futures-executor",
  "futures-io",
  "futures-sink",
  "futures-task",
@@ -247,6 +248,17 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
 
+[[package]]
+name = "futures-macro"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "futures-sink"
 version = "0.3.21"
@@ -268,6 +280,7 @@ dependencies = [
  "futures-channel",
  "futures-core",
  "futures-io",
+ "futures-macro",
  "futures-sink",
  "futures-task",
  "memchr",
@@ -302,7 +315,7 @@ dependencies = [
  "indexmap",
  "slab",
  "tokio",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
  "tracing",
 ]
 
@@ -461,6 +474,12 @@ version = "0.2.126"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
 
+[[package]]
+name = "libm"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
+
 [[package]]
 name = "lock_api"
 version = "0.4.7"
@@ -513,18 +532,19 @@ dependencies = [
  "tracing-futures",
  "tracing-opentelemetry",
  "tracing-subscriber",
+ "turmoil",
 ]
 
 [[package]]
 name = "mio"
-version = "0.8.3"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
 dependencies = [
  "libc",
  "log",
  "wasi",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -540,6 +560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
 dependencies = [
  "autocfg",
+ "libm",
 ]
 
 [[package]]
@@ -631,7 +652,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.36.1",
 ]
 
 [[package]]
@@ -813,6 +834,16 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "rand_distr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.13"
@@ -857,6 +888,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -980,22 +1017,22 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "1.19.2"
+version = "1.23.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
+checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8"
 dependencies = [
+ "autocfg",
  "bytes",
  "libc",
  "memchr",
  "mio",
  "num_cpus",
- "once_cell",
  "parking_lot",
  "pin-project-lite",
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "winapi",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1030,6 +1067,19 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-test"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
+dependencies = [
+ "async-stream",
+ "bytes",
+ "futures-core",
+ "tokio",
+ "tokio-stream",
+]
+
 [[package]]
 name = "tokio-util"
 version = "0.6.10"
@@ -1046,9 +1096,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
+checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
 dependencies = [
  "bytes",
  "futures-core",
@@ -1115,7 +1165,7 @@ dependencies = [
  "rand",
  "slab",
  "tokio",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
  "tower-layer",
  "tower-service",
  "tracing",
@@ -1226,6 +1276,24 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
 
+[[package]]
+name = "turmoil"
+version = "0.3.3"
+dependencies = [
+ "bytes",
+ "futures",
+ "indexmap",
+ "rand",
+ "rand_distr",
+ "scoped-tls",
+ "tokio",
+ "tokio-stream",
+ "tokio-test",
+ "tokio-util 0.7.4",
+ "tracing",
+ "tracing-subscriber",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.1"
@@ -1368,39 +1436,100 @@ version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
 dependencies = [
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_msvc",
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.42.0",
+ "windows_i686_gnu 0.42.0",
+ "windows_i686_msvc 0.42.0",
+ "windows_x86_64_gnu 0.42.0",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.0",
 ]
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index ef50e01..2e000d2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,9 +40,21 @@ opentelemetry-aws = { version = "0.5.0", optional = true }
 # Allows you to send data to the OTel collector
 opentelemetry-otlp = { version = "0.10.0", optional = true }
 
+# TODO: Make this cfg dep
+turmoil = "0.3"
+
 [dev-dependencies]
 # Enable test-utilities in dev mode only. This is mostly for tests.
 tokio = { version = "1", features = ["test-util"] }
 
 [features]
 otel = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-aws", "dep:opentelemetry-otlp"]
+
+[workspace]
+members = [
+  ".",
+  "xtask"
+]
+
+[patch.crates-io]
+turmoil = { path = "../turmoil" }
diff --git a/src/bin/cli.rs b/src/bin/cli.rs
index 4e7d354..98a7096 100644
--- a/src/bin/cli.rs
+++ b/src/bin/cli.rs
@@ -1,3 +1,5 @@
+#![cfg_attr(sim, allow(dead_code, unused_imports))]
+
 use mini_redis::{client, DEFAULT_PORT};
 
 use bytes::Bytes;
@@ -64,6 +66,9 @@ enum Command {
     },
 }
 
+#[cfg(sim)]
+fn main() {}
+
 /// Entry point for CLI tool.
 ///
 /// The `[tokio::main]` annotation signals that the Tokio runtime should be
@@ -73,6 +78,7 @@ enum Command {
 /// `flavor = "current_thread"` is used here to avoid spawning background
 /// threads. The CLI tool use case benefits more by being lighter instead of
 /// multi-threaded.
+#[cfg(not(sim))]
 #[tokio::main(flavor = "current_thread")]
 async fn main() -> mini_redis::Result<()> {
     // Enable logging
diff --git a/src/bin/server.rs b/src/bin/server.rs
index 701a1f1..068fbc6 100644
--- a/src/bin/server.rs
+++ b/src/bin/server.rs
@@ -5,6 +5,7 @@
 //! `mini_redis::server`.
 //!
 //! The `clap` crate is used for parsing arguments.
+#![cfg_attr(sim, allow(dead_code, unused_imports))]
 
 use mini_redis::{server, DEFAULT_PORT};
 
@@ -28,6 +29,10 @@ use tracing_subscriber::{
     fmt, layer::SubscriberExt, util::SubscriberInitExt, util::TryInitError, EnvFilter,
 };
 
+#[cfg(sim)]
+pub fn main() {}
+
+#[cfg(not(sim))]
 #[tokio::main]
 pub async fn main() -> mini_redis::Result<()> {
     set_up_logging()?;
diff --git a/src/blocking_client.rs b/src/blocking_client.rs
index 962a1e9..82ea06d 100644
--- a/src/blocking_client.rs
+++ b/src/blocking_client.rs
@@ -2,9 +2,10 @@
 //!
 //! Provides a blocking connect and methods for issuing the supported commands.
 
+use crate::connection::ToSocketAddrs;
+
 use bytes::Bytes;
 use std::time::Duration;
-use tokio::net::ToSocketAddrs;
 use tokio::runtime::Runtime;
 
 pub use crate::client::Message;
diff --git a/src/client.rs b/src/client.rs
index 2c749fb..bfbea6a 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -5,11 +5,12 @@
 use crate::cmd::{Get, Ping, Publish, Set, Subscribe, Unsubscribe};
 use crate::{Connection, Frame};
 
+use crate::connection::{TcpStream, ToSocketAddrs};
+
 use async_stream::try_stream;
 use bytes::Bytes;
 use std::io::{Error, ErrorKind};
 use std::time::Duration;
-use tokio::net::{TcpStream, ToSocketAddrs};
 use tokio_stream::Stream;
 use tracing::{debug, instrument};
 
diff --git a/src/connection.rs b/src/connection.rs
index 64c11c8..ab02f80 100644
--- a/src/connection.rs
+++ b/src/connection.rs
@@ -3,7 +3,14 @@ use crate::frame::{self, Frame};
 use bytes::{Buf, BytesMut};
 use std::io::{self, Cursor};
 use tokio::io::{AsyncReadExt, AsyncWriteExt, BufWriter};
-use tokio::net::TcpStream;
+
+#[cfg(not(sim))]
+pub use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
+#[cfg(sim)]
+pub use turmoil::{
+    net::{TcpListener, TcpStream},
+    ToSocketAddrs,
+};
 
 /// Send and receive `Frame` values from a remote peer.
 ///
diff --git a/src/server.rs b/src/server.rs
index 37bacfd..d37c1a8 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -3,18 +3,18 @@
 //! Provides an async `run` function that listens for inbound connections,
 //! spawning a task per connection.
 
+use crate::connection::{TcpListener, TcpStream};
 use crate::{Command, Connection, Db, DbDropGuard, Shutdown};
 
 use std::future::Future;
 use std::sync::Arc;
-use tokio::net::{TcpListener, TcpStream};
 use tokio::sync::{broadcast, mpsc, Semaphore};
 use tokio::time::{self, Duration};
 use tracing::{debug, error, info, instrument};
 
 /// Server listener state. Created in the `run` call. It includes a `run` method
 /// which performs the TCP listening and initialization of per-connection state.
-#[derive(Debug)]
+//#[derive(Debug)]
 struct Listener {
     /// Shared database handle.
     ///
diff --git a/tests/buffer.rs b/tests/buffer.rs
index 823b720..b658c4c 100644
--- a/tests/buffer.rs
+++ b/tests/buffer.rs
@@ -1,3 +1,5 @@
+#![cfg(not(sim))]
+
 use mini_redis::{buffer, client, server};
 use std::net::SocketAddr;
 use tokio::net::TcpListener;
diff --git a/tests/client.rs b/tests/client.rs
index 4d8d127..1c7eafa 100644
--- a/tests/client.rs
+++ b/tests/client.rs
@@ -1,3 +1,5 @@
+#![cfg(not(sim))]
+
 use mini_redis::{client, server};
 use std::net::SocketAddr;
 use tokio::net::TcpListener;
diff --git a/tests/server.rs b/tests/server.rs
index 488cb58..c279e3e 100644
--- a/tests/server.rs
+++ b/tests/server.rs
@@ -1,3 +1,5 @@
+#![cfg(not(sim))]
+
 use mini_redis::server;
 
 use std::net::SocketAddr;
diff --git a/tests/sim.rs b/tests/sim.rs
new file mode 100644
index 0000000..6dec2f9
--- /dev/null
+++ b/tests/sim.rs
@@ -0,0 +1,29 @@
+#![cfg(sim)]
+
+use mini_redis::{client, server};
+use turmoil::Builder;
+
+#[test]
+fn smoke() {
+    let mut sim = Builder::new().build();
+
+    const HOST: (&str, u16) = ("127.0.0.0", 6379);
+
+    sim.host("server", || async {
+        let listener = turmoil::net::TcpListener::bind(HOST).await.unwrap();
+
+        server::run(listener, std::future::pending::<()>()).await;
+    });
+
+    sim.client("client", async {
+        // TODO: ? doesn't work here for some reason
+        let mut client = client::connect(HOST).await.unwrap();
+
+        client.set("hello", "world".into()).await.unwrap();
+        let result = client.get("hello").await.unwrap().unwrap();
+
+        assert_eq!(&result[..], &b"world"[..]);
+
+        Ok(())
+    });
+}
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
new file mode 100644
index 0000000..d9dff72
--- /dev/null
+++ b/xtask/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
new file mode 100644
index 0000000..2d9d3cc
--- /dev/null
+++ b/xtask/src/main.rs
@@ -0,0 +1,54 @@
+use std::{
+    env,
+    path::{Path, PathBuf},
+    process::Command,
+};
+
+type DynError = Box<dyn std::error::Error>;
+
+fn main() {
+    if let Err(e) = try_main() {
+        eprintln!("{}", e);
+        std::process::exit(-1);
+    }
+}
+
+fn try_main() -> Result<(), DynError> {
+    let task = env::args().nth(1);
+    match task.as_ref().map(|it| it.as_str()) {
+        Some("sim") => sim()?,
+        _ => print_help(),
+    }
+    Ok(())
+}
+
+fn print_help() {
+    eprintln!(
+        "Tasks:
+sim             runs turmoil based simulation tests
+"
+    )
+}
+
+fn sim() -> Result<(), DynError> {
+    let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
+    let status = Command::new(cargo)
+        .env("RUSTFLAGS", "--cfg tokio_unstable --cfg sim")
+        .current_dir(project_root())
+        .args(&["test", "--tests"])
+        .status()?;
+
+    if !status.success() {
+        Err("cargo build failed")?;
+    }
+
+    Ok(())
+}
+
+fn project_root() -> PathBuf {
+    Path::new(&env!("CARGO_MANIFEST_DIR"))
+        .ancestors()
+        .nth(1)
+        .unwrap()
+        .to_path_buf()
+}