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.


Using first class modules to test in ReasonML
#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?


#9

@bsommardahl they are similar. Using the first-class modules approach allow you to get a module dynamically at runtime. So in my case, I’m setting up an API endpoint that should use mocks only when I’m running tests. I’ll set some header on the HTTP request (“X-USE-MOCKS”) or something, and inside the endpoint, I’ll call a function that will give me back either a concrete module, or a mock module depending on the value of the header. So in this case, I can actually run my test suite against production, and the mock module will be used for just those requests, which is pretty neat!