Mocking in Motoko unit tests?

I was just starting to learn about testing in Motoko, and love the functional aspect of it.

The matchers library works for most cases, but for the case where I’m writing a function that utilizes several other functions or using a third party library, I’d like for the opportunity to mock out those third party libraries, so I can strictly test the unit, or the logic of the top level function that I am writing.

For example, let’s say I’m using a UUID and Time library and want to mock out the implementation of those, so I can control their output.

module.mo

import Time "mo:base/Time";
import Text "mo:base/Text";
import UUID "../src/UUID";

module {
  public func createUser(name: Text): MyUserType {
    let time = Time.now();
    let id = UUID.create();
    return {
      time: time,
      id: id,
      name: text
    } 
  }
}

I started playing around with rebinding the functions inside a Module like so

import UUID "../src/UUID";

actor {
  public func tryMock(): async () {
    UUID.create = func() {
      return "1";
    };
  }
}

And got the error, type error [M0073], expected mutable assignment target.

This leads me to believe that all declared functions inside a module are immutable. Any declaration of a mutable variable within a module returns the error, type error [M0014], non-static expression in library or module.

I’ve tried a few other “hacky” approaches, but haven’t been able to rebind or overwrite the module itself so that it is “mocked” in tests.

My questions then are:

  1. Am I missing something, is mocking at all possible in Motoko?
  2. Is the intention for mocking/stubbing to not be supported in Motoko, and to prefer other testing patterns?
  3. What is the recommended way for unit testing this type of scenario? (Hopefully something other than integration testing).
1 Like

I believe IC Kit does something like what you’re trying to do for Rust so perhaps something can be learned from that.

The simplest way would be to pass in the things you’d like to mock. Something like:

module {
  type Env = {
    Time : {
      now : () -> Time;
    };
    UUID : {
      create : () -> UUID;
    };
  };

  func public func createUser(env : Env, name : Text) : MyUserType {
    let time = env.Time.now();
    let id = env.UUID.create();

    return {
      time: time,
      id: id,
      name: text
    } 
  };
}

Then during testing, provide whatever env you like.

1 Like

Makes sense, and could definitely see this working out for different production stages. I would prefer a solution that doesn’t require adding an additional parameter to every function in the module, but this solution is a good starting place!

In fact, this approach probably lends itself better to either a class approach with the env as an instance variable initialized in the constructor (or a wrapper function that returns an object containing all the accessible items in the module), so that the env can be shared between all the functions in the module.