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.
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?
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
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)
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.
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”)