Best practice for stubbing

Hi, I’m going to ask something which, probably is guided by mi Object Oriented background and now that I’m trying to learn functional programming I’m just doing it wrong.

Suppose I have a function like:

submitLogin:  String -> String -> String -> User
submitLogin apiUrl username password =
...

Now, I want to have several implementations of this function, so that in development, I replace this by a “stubbed” or “hardcoded” implementation which will always return a Test/Demo user, so that I don’t need to always have my backend running when not really developing a feature related to it. Let’s suppose this is not always HTTP, like it could be rendering a tag for Google Analytics, etc.

My noob approach was to do something like

submitLogin:  Bool ->String -> String -> String -> User
submitLogin useStub apiUrl username password =
  case useStub of
    True ->
        ... -- Return hardcoded User
    False ->
       ...  -- Do the real thing

Is there any recommended pattern or better way to do this?

Thanks a lot for your help.

OK, so submitLogin will have a type like this

submitLogin: Username -> Password -> Cmd Msg

There really is no need for stubbing/mocking in Elm because everything is pure. What this function will do is give you a command which will then create an event later to return the data (or an error message). The type User will only ever contain a User data structure.

Even if it is pushing something out to google analytics it is still going to give you a command.

Zach
If anyone needs an extra elm developer please give me a shout

You can create another version of the function, so long as it has the same type it can be substituted anywhere the original can go.

submitLogin:  String -> String -> String -> User
submitLogin apiUrl username password =
...

stubbedSubmitLogin:  String -> String -> String -> User
stubbedSubmitLogin apiUrl username password =
...

An anlogy is interfaces and classes in Java. An interface defines the type, and you can them implement >1 class with that type and swap implementations.

The other thing you should know is if something returns User then it will always return a User, if it is doing some form of searching for a User It should return Maybe User (Or Result Error User)

Thank you all for your answers.

In the end, I think there is no “magic” possible (and that’s a good thing), because whatever I do, at some point I need an if/case to decide from within the function, that it returns a hardcoded User or a real one. For example in rupert’s example, I still would need a wrapper function with a case for selecting which one of the two functions gets called.

I just wanted to make sure there isn’t something like having a curried function stored somewhere (in the model? sound like a bad idea…) so that all other code could call this one without knowing if they are calling the stubbed one or the real one.

Thanks!

I think the solution is very context dependent. Here’s a few solutions in the order I would try them:

Just use the backend

Assuming you control the backend (is the Elm app served by the backend?) you should be able to create a user in your local dev system and use that without making any changes to the Elm code. This is by far the simplest solution and the one I go for first when developing Elm apps.

Pros:

  • Simple
  • You’re probably already running a local server if the backend hosts the Elm app
  • Don’t need to change Elm code
  • Making real (local) HTTP requests so you’re using the full stack

Cons:

  • You need to control the backend
  • You need to run a local server

Fake the backend

If you’re developing against a third-party service, I like to create a small fake app that I’ll run locally that responds to the same API but returns canned responses. I’ve used this technique a lot when integrating backends with third-party services that don’t have a sandbox.

Pros

  • Don’t need to change Elm code
  • Making real (local) HTTP requests so you’re using the full stack

Cons

  • You need to run a local server
  • You need to write a fake service

Conditional in the Elm code

Given the following types:

type alias Stubbable a
  = Hardcoded a
  | GetFromApi

type alias Config =
  { user : Stubbable User
  }

type alias Model =
  { config : Config
  , currentUser : User
  }

you’d probably build up the the Config record based on flags passed into the app. Then you could write this task-based approach for switching:

fetchUser : Config -> Int -> Cmd Msg
fetchUser config id =
  case config.user of
    Hardcoded user ->
      Task.attempt UserFetched (fetchHardcodedUser user)

    GetFromApi ->
      Task.attempt UserFetched (fetchUserFromApi id)

fetchUserFromApi : Int -> Task Http.Error User
fetchuserFromApi id =
  Http.get ("http://someservice.com/user/" ++ id) userDecoder
    |> Http.toTask

fetchHardcodedUser : User -> Task a User
fetchHardcodedUser user =
  Task.succeed user

Pros

  • More fine-grained control
  • Don’t need to run a local server

Cons

  • Need to change the Elm code
  • Doesn’t exercise the full stack (HTTP could be unimplemented and it would still “work on my machine”)
2 Likes

@joelq Awesome answer, thanks a lot.