How to maintain generic interface and support testability

I'm very new to Rust, so please forgive me if I'm using the wrong concepts and errors in the code sample.

I've got an application that I'm instrumenting telemetry for, but I'd like to avoid using references to the concrete client in my application code such that it could be configurable (particularly for testing purposes. I'm not sure if I'm doing this correctly, but here are my goals and some (slimmed down) code for reference. Looking for pointers and generally an idea about how people typically support testing

Goals:

  1. avoid use of concrete client, because the client talks to a daemon that isn't necessary for testing purposes (want to use a vector in its place as the storage mechanism), so I essentially want to have it stubbed out
  2. avoid having to pass the client around and add a parameter to every function or a field in the application struct -- attempted to use a global ref via lazy_static but my understanding is lacking and can't get this to work (due to thread-safety around a trait object maybe?)
  3. in testing, I just want to wrap a vector and make assertions against it

Code:

// lib.rs
use statsd::Client;

pub trait Collector {
    fn increment_counter(&self, counter: &str);
}

impl Collector for statsd::Client {
    fn increment_counter(&mut self, counter: &str) {
        self.incr(counter);
    }
}

impl Collector for Vec<_> {
    fn increment_counter(&mut self, counter: &str) {
        self.push(counter);
    }
}

// app.rs
struct App;

impl App {
    fn start() -> Self {}

    fn foo() {
        let t = TELEMETRY.lock().map_err(|_| "unable to acquire lock");
        t.increment_counter("foo");
    }

    fn bar() {
        let t = TELEMETRY.lock().map_err(|_| "unable to acquire lock");
        t.increment_counter("bar");
    }
}

// main.rs
#[macro_use]
mod App;
extern crate lazy_static;
use std::sync::Mutex;

// NOTE: this has me hung up 
lazy_static! {
    static ref TELEMETRY: Mutex<dyn Collector> = {
        let config = Config::new();
        if config.client == None {
            Mutex::new(statsd::Client::new(config.host, config.prefix))
        } else { // used in test or other scenarios
            Mutex::new(Vec::new())
        }
    };
}

fn main() {
    App::start();
}

#[test]
mod tests {
    fn test_telemetry() {
        let app = App::start();
        app.foo();

        let t = TELEMETRY.lock().map_err(|_| "unable to acquire lock");
        assert_eq!(&t[0], "foo");
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.