asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

::new() no matching expectation found #424

Closed n-0 closed 1 year ago

n-0 commented 1 year ago

Rust version rustc 1.61.0 Mockall version 0.11.3

I tried to mock a method named new of a struct like this

mock! {
  pub ExternalStruct {
    pub fn new() -> Self;
  }
}

MockExternalStruct::new_context().expect().returning(|| MockExternalStruct::default())
// and calling somewhere else
let mocked = ExternalStruct::new()

The execution panics with

panicked at 'MockExternalStruct::new()
No matching expectation found`

What am I missing?

n-0 commented 1 year ago

This issue seems not to be specific to the name as other methods with the same signature fail to find an expectation. E.g.

mock! {
  pub ExternalStruct {
    pub fn build() -> Self;
  }
}

Results in the same issue

asomers commented 1 year ago

The Context object returned by the new_context method does important stuff when it drops. But in your example, you drop it immediately as soon as you create it. Try like this instead:

let ctx = MockExternalStruct::new_context();
ctx.expect().returning(|| MockExternalStruct::default());
...
let mocked = ExternalStruct::new();
n-0 commented 1 year ago

Maybe I missed something about the asynchronous execution and it is dropped, the code looks more like this

// file to test
pub struct Session {
  pub external_struct: ExternalStruct
}

impl Session {
  pub fn async setup() -> Result<Self, Box<dyn Error>> {
      let mut external_struct = ExternalStruct::new();
      external_struct::connect().await?;
      Session { external_struct }
  }
}
// test file
#[tokio::test]
pub fn it_setups() {
   // ...
   // ... locking contexts with lazy static sync logic before
   let ctx = MockExternalStruct::new_context();
   ctx.expect().returning(|| {
     let mock = MockExternalStruct::default();
     mock.expect_connect().returning(|| Ok(());
     mock
    });
   let session = Session::setup().await;
   assert!(session.is_ok());
}

#[tokio::test]
pub fn other_tests_also_using_new_context {
   // ...
   // ... locking contexts with lazy static sync logic before
}

For now I worked around it by not mocking the static methods in this specific case (because it worked like a charm for other structs and signatures otherwise) and instead did

#[cfg(not(test))]
fn create_external() -> ExternalStruct {
    ExternalStruct::new()
}

#[cfg(test)]
fn create_external() -> ExternalStruct {
     let mock = ExternalStruct::default();
     mock.expect_connect().returning(|| Ok(());
     mock
}

impl Session {
  pub fn async setup() -> Result<Self, Box<dyn Error>> {
      let mut external_struct = create_external();
      external_struct::connect().await?;
      Session { external_struct }
  }
}
n-0 commented 1 year ago

just tested with the original given example and yes definitely dropped it, so thank you that fixed it!