asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.45k stars 61 forks source link

Struggling to mock trait objects and make assertions on functions that take ownership of mock trait object #546

Closed nectariner closed 7 months ago

nectariner commented 7 months ago

I'm wondering if it's possible to use mock implementation of traits as trait objects owned by a struct. I've been trying this but can't find a way of providing a mock implementation to the struct that has set return values, etc, and then can't make assertions on said trait object because of moves. Any help appreciated - thanks (I've attached some code around what I've been trying)

#[automock]
#[async_trait]
pub trait DataPuller {
    async fn get_all_books(&self) -> Result<Vec<crate::model::book::Book>, anyhow::Error>;
    async fn get_book_by_isbn(
        &self,
        isbn: &crate::model::isbn::ISBN,
    ) -> anyhow::Result<Option<crate::model::book::Book>>;
    async fn update_book_by_isbn(&self, new_book: &Book) -> anyhow::Result<()>;
    async fn insert_book(&self, new_book: &Book) -> anyhow::Result<()>;
}

--------------------------------

#[derive(Clone)]
pub struct AppState {
    pub db_client: Arc<dyn DataPuller + Send + Sync>,
}

#[derive(Clone)]
pub struct SharedState(pub Arc<RwLock<AppState>>);

impl SharedState {
    pub fn new(app_state: AppState) -> Self {
        Self(Arc::new(RwLock::new(app_state)))
    }
}

-------------------------------- test below

#[tokio::test]
async fn get_foo() {
    // mockConnect
    let puller = MockDataPuller::new();
    // How to set puller.get_all_books return value?
    let response = get(State(SharedState::new(AppState {
        db_client: Arc::new(puller),
    })))
    .await
    .unwrap();
    assert_eq!(response.0, vec![]);
    // how to assert puller.get_all_books called only once?
}
asomers commented 7 months ago

This is easy. Being a trait object doesn't change the way that you set expectations. Just do it like you would for any other mock object:

let mut puller = MockDataPuller::new();
puller.expect_get_all_boooks()
    .times(1)
    .returning(|| ...)
nectariner commented 7 months ago

Yes but this then has the type Expectation (which doesn't implement the required trait) so can't be provided to the struct containing the trait object

Also I really appreciate the quick reply

asomers commented 7 months ago

Obviously, don't construct AppState from the Expectation. Construct it from the Mock object.

nectariner commented 7 months ago

My apologies - I completely misunderstood the docs. I was doing something

let foo = MockFooImplementation::new().expect_my_function().times(1);
let my_struct = S::new(foo);

My bad!