Elm-program-test, simulated effects, and functors, kind of

I just posted a blog post describing a pattern I’m using with elm-program-test which I thought might be interesting to anyone using elm-program-test.

In elm-program-test you simulate effects by calling the elm-program-test API to create simulated versions of the effects that you have in your main program. To make this convenient, elm-program-test provides similar (ideally identical) APIs for the simulated effect as you’re likely using for the real effect. So for example SimulatedEffect.Http has the same API as Http (from elm/http).

This is great, but you might have some interesting logic that goes into creating the effect, which you don’t really want to duplicate for your main program and your tests. One way to alleviate this would be for elm-program-test to provide a way to translate SimulatedEffect.Http.Requests into Http.Requests. That way, your main program and test program both build up a SimulatedEffect.Http.Request and then the main program just calls SimulatedEffect.translate. The disadvantage of that is that it introduces an unnecessary abstraction to your main program. Additionally, not all effects are currently simulated. It would be better if elm-program-test could write a translator from Http.Requests to SimulatedEffect.Http.Requests, but it cannot because the type of Http.Requests is opaque to it.

The solution I’ve come up with, is to write the module for your main program, which generates the Http.Requests, and then write a script which translates this module into an equivalent module that generates SimulatedEffect.Requests. It turns out that writing such a script is pretty trivial, since mostly what you need to do is change import Http to import SimulatedEffect.Http as Http. In this sense they work like SML/Ocaml functors.

1 Like

Firstly, thank you so much for this post. Always love to see more people writing about Elm!


For the problem you mention, would it have been possible to instead change the implementation of your http requests to be something like

type Effect
    = GetPosts { url : String, expect : Http.Expect (List Post) }
    ...


perform : Model -> Effect -> Cmd Msg
perform model effect =
    case effect of
        GetPosts request -> Http.get request
        ...


simulate : Model -> Effect -> SimulatedEffect Msg
simulate model effect =
    case effect of
        GetPosts request -> SimulatedHttp.get request

You could also abstract that out a slight bit more to have it just be Http.request and SimulatedHttp.request. Then you wouldn’t need to redefine the body of the requests. You could even make it so that in your app that you never write GetPosts { ... } but instead of something like

module Api exposing (..)

getPosts : Effect
getPosts =
    GetPosts { ... }

and you could configure elm-review to only allow your Effect constructors to be used in Api and select other modules.

1 Like

Hi,

Yes you can certainly start going down this direction. One issue is that SimulatedEffect.Http.Expect is not the same as Http.Expect. So the exact thing you describe won’t work, because you will have no way to translate the Http.Expect into a SimulatedEffect.Http.Expect.

So what you could do is come up with your own Expect type and then translate that (in the trivial way) to both Http.Expect and SimulatedEffect.Http.Expect and from there obviously you can easily make an Http.Request and a SimulatedEffect.Http.Request.

With this approach what you would end up doing is creating a third representation of requests. Not only that, but your translations from the third representation into the two you care about would be almost identical. So, you see where this is going.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.