What is your approach to stubs/fakes in ReasonML?


#1

As I am unit testing my code, I sometimes need to fake a dependency. How do you handle things like this? Any example code would be especially helpful.


#2

We have typically been using Functors as a form of dependency injection.

module type DependenciesOfA = {
    module Repository : {
        let saveString: string => Js.Promise.t(unit);
    };
};

module A = (Services: DependenciesOfA) => {
  let doSomething = (arg) => Services.Repository.saveString(arg);
};

module ConcreteRepository = {
  let saveString = str => Js.Promise.resolve();
};

module ConcreteADependencies = {
 module Repository = ConcreteRepository;
};

include A(ConcreteADependencies);

This allows us to easily inject stubs/fakes (also helps that we’re utilising the actor model :stuck_out_tongue: which is itself a form of indirection)

My example is probably a little messy. Can probably be written much more neatly


#3

Could you show how you are injecting a stub/fake repository in a test?


#5

Here is a concrete example used in a real test. The module at the bottom is the one used in the tests:

module MockedUserRepo = {
  let users = ref(Authorization.UserId.Map.empty);
  let usersFromList = (users: list(Authorization.User.t)) =>
    List.fold_left(
      (prev, user: Authorization.User.t) =>
        Authorization.UserId.Map.add(user.userId, user, prev),
      Authorization.UserId.Map.empty,
      users
    );
  let userToT: Authorization.User.t => User.t =
    (user: Authorization.User.t) => {
      userId: user.userId,
      firstName: "Hiero",
      lastName: "Protagonist",
      email: "hiero@prota.com"
    };
  let setUsers = (u: list(Authorization.User.t)) => users := usersFromList(u);
  let getUser = userId =>
    return(
      try (Some(Authorization.UserId.Map.find(userId, users^) |> userToT)) {
      | Not_found => None
      }
    );
  let getUsers = userIds => {
    let userIdSet = Authorization.UserId.Set.of_list(userIds);
    let users =
      users^
      |> Authorization.UserId.Map.filter((k, _) =>
           Authorization.UserId.Set.exists(
             Authorization.UserId.equal(k),
             userIdSet
           )
         );
    return(
      Authorization.UserId.Map.fold(
        (_, v, prev) => [v |> userToT, ...prev],
        users,
        []
      )
    );
  };
};

module MockS3Uploader = {
  let uploadClientAppIcon = (name, _orgId, _data) => {
    return(Js.Result.Ok(name));
  };
};

module MockedClientAppRepo = {
  let addOrUpdateClientApp = _org => return();
  let getClientApp = _orgId => {
    let clientApp: Authorization.ClientApp.t = {
      isReviewed: true,
      secret: ""
    };

    return(clientApp);
  };
};

module MockedOrgActor = OrganizationActor.Make(MockedClientAppRepo, MockedUserRepo, MockS3Uploader);

#6

I came up with an example showing how to use first class modules rather than Functors in discord, thought it might be useful to post here as well


#7

Hah, I was gonna say “oh boy introducing first-class modules directly when a newcomer needs mocking isn’t great”, but on second reading, this seems like a viable pattern…


#8

What might be the benefits of using first class modules over functors in this case?