A crate boundary is easier to test when application code depends on a trait instead of a concrete external client.

Program

Play the program to choose a retry count and see the client wrapped behind a small trait.

dependency_adapter.rs
fn main() {
    let retries = ;
    let client = MockClient { status: "ok" };
    let message = fetch_status(&client, retries);
    println!("{message}");
}

trait StatusClient {
    fn status(&self) -> &'static str;
}

struct MockClient {
    status: &'static str,
}

impl StatusClient for MockClient {
    fn status(&self) -> &'static str {
        self.status
    }
}

fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
    format!("{} after {} retries", client.status(), retries)
}
fn main() {
    let retries = ;
    let client = MockClient { status: "ok" };
    let message = fetch_status(&client, retries);
    println!("{message}");
}

trait StatusClient {
    fn status(&self) -> &'static str;
}

struct MockClient {
    status: &'static str,
}

impl StatusClient for MockClient {
    fn status(&self) -> &'static str {
        self.status
    }
}

fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
    format!("{} after {} retries", client.status(), retries)
}
fn main() {
    let retries = ;
    let client = MockClient { status: "ok" };
    let message = fetch_status(&client, retries);
    println!("{message}");
}

trait StatusClient {
    fn status(&self) -> &'static str;
}

struct MockClient {
    status: &'static str,
}

impl StatusClient for MockClient {
    fn status(&self) -> &'static str {
        self.status
    }
}

fn fetch_status(client: &impl StatusClient, retries: i32) -> String {
    format!("{} after {} retries", client.status(), retries)
}
adapter `StatusClient` is the stable boundary that application code depends on.
mock `MockClient` lets the example run without a network client or third-party crate.
dependency seam `fetch_status` can accept another client later if it implements the same trait.