Unit testing Rust canisters

I’m currently writing tests for Sudograph, using Rust’s built-in testing capabilities (cargo test and such) and proptest. I spin up a local replica and execute my tests as query or update calls into canisters. That works very well for integration tests, as my tests go through the entire motions of creating query and update calls and validating the responses.

But it’s very slow, even if I use --no-artificial-delay, compared to the speed my tests could move at if they were running from within the canister. Instead of performing http requests to call the canister methods, it would be nice if I could run tests from within the canister that could call the methods directly.

How should we go about testing canister code without the overhead of network requests to perform query and update calls?

I personally find it very useful to keep API methods as simple as possible and have a separate place for the main logic, which I can cover with standard rust unit tests.

For example, consider the following canister

static mut STATE: Option<InfoStorage> = None;

#[init]
fn init() {
  unsafe {
    STATE = Some(InfoStorage::new())
  }
}

#[update]
fn set_info(info: String) {
  let state = unsafe { STATE.as_mut().unwrap() };
  state.set_info(info);
}

#[query]
fn get_info() -> String {
  unsafe { STATE.as_ref().unwrap().get_info() }
}

I would suggest to have a separate struct InfoStorage in another file which you could cover with tests:

struct InfoStorage {
  pub info: String;
}

impl InfoStorage {
  pub fn new() { ... }

  pub fn set_info(info: String) { ... }

  pub fn get_info() -> String { ... }
}

#[cfg(test)]
mod tests {
  #[test]
  fn check_if_everything_works_ok() {
    let storage = InfoStorage::new();
    ...
  }
}
2 Likes