Oh, the Mockery! A Guide to HTTP Mocking in Elm

HTTP mocking is super useful for rapid prototyping, even in a functional language. Here’s 4 different ways you can mock your API calls in Elm.

Photo by Karim MANJRA on Unsplash

In most languages, HTTP mocking is crucial when testing your code, as it allows you to test your code in isolation without any external dependencies. However, it’s much less of a necessity in Elm due to Elm’s functional nature — you’re always testing isolated functions to begin with!

Still, HTTP mocking can be useful in many ways. Maybe you want to…

  • Run your application on your local machine without having to run the back-end, database, and all the other required dependencies.
  • Quickly prototype something and the APIs you need aren’t developed yet.
  • Visually see how your front-end responds to various HTTP responses and errors, before writing out complete test cases.
  • Mock some odd behaviour, like a slow response.

This guide will cover 4 different ways that you can mock HTTP responses in Elm. Let’s dive right in!

HTTP Mocking in Elm

Throughout this guide, I’ll be using https://jsonplaceholder.typicode.com as a free REST API. Refer to its documentation as needed!

There will be a lot of code throughout this guide, and it may be difficult to follow at times — don’t worry if it doesn’t click right away! I’ll try my best to explain it, and I also provide full Ellie examples at the end of each section.

Approach #1: Static Files

Ah, good old static JSON files. If you don’t know how this works, it’s incredibly simple:

  • Create a folder and put a punch of mock JSON responses in it. We’ll name our folder mocks.
  • When running our app in a test or mock environment, fetch the static files from our mocks folder as the response rather than making real API calls.

For example, to mock a response from a GET request sent to /posts/1, we’d create the file below under the path /mocks/posts/1.json .

Now, we just need to differentiate between environments appropriately. There’s many ways you could do that — for example, one thing we could do is store the environment and API URL in our model (they would be passed in as Flags when we initialize our app). We set the API URL to your mocks folder when in a testing or mock environment. Here’s a short code example:

This approach is great for mocking something quickly, but there’s two glaring issues with it:

  • There’s no way to mock errors. Either the file exists, in which case the HTTP request results in a 200 OK, or it doesn’t, resulting in a 404 Not Found. There’s no way to mock a response with a custom status code and body, or mock other behaviour like a delayed response, timeout, etc.
  • There’s no way to mock any method aside from a GET request. Without a dedicated mock server, you won’t be able to handle any other methods. Try sending a POST request to your dev server — you’ll probably get a 405 Method Not Allowed!

While obviously quite limited, there’s nothing wrong with some static JSON files if you just need to test an API quickly or mock something that hasn’t been implemented yet! However, you’ll probably want to replace this with something more robust as your project matures — the next few techniques I’ll discuss address the issues with this method.

Interlude: Understanding elm/http

The next two approaches require certain functions from elm/http. This section will give some insight into what these functions do, but if you’re already very familiar with elm/http, then by all means skip this and read ahead! You could also refer to the elm/http docs if you find that easier — whatever works for you!

When you send an HTTP request in Elm, the Elm runtime represents the response using the Http.Response type (I will refer to this as Response). However, you should know that to handle an HTTP response in Elm, you need to define a Msg with type Result x a. For example, type Msg = OnApiResponse (Result Http.Error String).

How do we go from Response to Result? elm/http provides some functions you may be familiar with, like expectString and expectJson, that do this conversion for you. However, you can choose instead to implement custom behaviour using two provided functions, expectStringResponse and expectBytesResponse.

These two functions are crucial for us to understand. They are are duals, with the only difference being how you want to interpret your HTTP response — as a String, or as Bytes? We’ll work with expectStringResponse since Strings are human-readable. Here’s its type signature:

expectStringResponse : (Result x a -> msg) -> (Response String -> Result x a) -> Expect msg

The first argument is the type constructor for our Msg, for example, OnApiResponse. The second is a function that converts a Response into a Result. Let’s call this a transformer. Finally, the output is an opaque type Expect, which is used by the Elm runtime internally. This is type that must be passed on every HTTP request that you send (see the expect field when using Http.get, Http.request, etc.).

Here’s a quick example taken from the elm/http docs showing how you could use expectStringResponse to implement expectJson.

OK, fine… but how does this help us with HTTP mocking?

The key is the second argument, which I called a transformer. In the code above, the transformer is the anonymous function with the long case statement. It transforms the Response into Result Http.Error a.

We can take advantage of having this transformer function available, and use it to “mock” an HTTP response by providing a Result that might not necessarily map to the Response that’s provided by the Elm runtime!

Photo by Arseny Togulev on Unsplash

The next two approaches will use expectStringResponse and these transformers to mock HTTP responses.

Approach #2: “Unboxing” the Response Body

Like the first approach, this method also uses static files, but it comes with a twist that rectifies one of the issues with the former method. We saw that we weren’t able to mock custom errors when using static files. That is, if it’s a regular static file… what if we structured the contents of the file in a special way?

Typically, the body of an HTTP response will give us some information we need. Let’s consider the response body as a box. Inside, it can hold either a regular response from our server, like 1.json from the first approach, or contain another response inside it.

Confused? Let’s try explaining it using an illustration. When we fetch our static JSON file, we get a 200 status code with some body. We try to “unbox” this body — it might contain some other response. Here, the “boxed” body is telling us that we want to mock an error with a 401 status code!

               Boxed Response               Unboxed Response
HTTP Request ┌─────────────┐ Unbox ┌─────────────┐
============> │ 200 │ ==========> │ 401 │
╞═════════════╡ ╞═════════════╡
│ ┌──────┐ │ │ data │
│ │ 401 │ │ └─────────────┘
│ ╞══════╡ │
│ │ data │ │
│ └──────┘ │
└─────────────┘

If the response doesn’t contain any “boxed” response though, we just pass it through intact, without changing anything.

               Regular Response              Unboxed Response
HTTP Request ┌─────────────┐ Unbox ┌─────────────┐
============> │ 200 │ ==========> │ 200 │
╞═════════════╡ ╞═════════════╡
│ data │ │ data │
└─────────────┘ └─────────────┘

Good idea — but how do we accomplish this? As you should know, when working with JSON in Elm, we always need to define a structure based on what we expect the response to look like, and then run a decoder that tries to convert the data we receive into the data structure that we’ve defined.

The basic concept here is that we need to define a standard structure for what a “boxed” response looks like, and try to decode the response body into that structure! Let’s suppose that a “boxed” response has the following JSON body:

type alias BoxedResponse =
{ responseType: String
, metadata : Metadata
, body : String
}

For example, here’s a “boxed” response as a JSON that represents a 401 error with a custom error message in the body.

We can then run a custom decoder on the response body (i.e, “unbox” the body). If it succeeds, we mock a response according to the “boxed”response, which could be something other than a 200 OK! Otherwise, we pass through the original body intact.

To fully realize this, we use expectStringResponse and implement a transformer that first runs our custom decoder on the body of the Response. Here’s a full working example that demonstrates how to “unbox” a response and mock a response accordingly.

In this example, we mock a 401 Unauthorized using a “boxed” response, but we can still handle real API calls without a problem.

To recap, this approach allows us to mock custom HTTP errors, which was one of the limitations of the first approach. However, note that we’re still unable to mock methods other than GET as we have to fetch a static file, making this approach still somewhat limited.

Credit for the idea behind this goes to my colleague Lucamug. You can read his article on this approach here, or check out a package that we worked on that includes an API for this approach (although it’s written for v1.0 of elm/http).

Approach #3: Mocking Responses Directly in Elm

Similar to the previous section, this approach also implements a custom transformer to mock a response. However, it removes the dependency of static files completely, and instead directly hijacks the behaviour of elm/http to replace the real response we receive with whatever mocked response we want without the need for static files!

The idea behind this approach is to “exploit” expectStringResponse so that it always runs the transformer on a Response that we create in Elm, rather than the real Response that is given by the Elm runtime (i.e. the actual response from your HTTP request)!

We accomplish this by implementing a function that we’ll call expectStringResponseMocked. It looks very similar to expectStringResponse but requires an additional argument, a Response. This Response is the response that you’d like to mock! It’s implementation is laughably simple:

All we do is call our transformer with the mock Response provided in Elm instead of the real one! It still creates the Expect type that we need, but it will call the transformer on the mock response instead of the one that’s actually received.

More technically, we “exploit” expectStringResponse through partial function application. Not so much an exploitation, as much as it is an example of the beauty of functional programming! Here’s a full working example using this approach.

In this example, I mock a Timeout and NetworkError from within Elm. You can try to edit the code and mock other responses!

Some things to notice:

  • It literally doesn’t matter what HTTP request we make, since we’ll be mocking a different response anyways. I could send a GET request to example.com, and we’ll still mock whatever we want! Don’t believe me? Try it out yourself!
  • We’re now able to mock different methods. Even if I POST localhost:5000 and it fails, I will still mock the response from within Elm and discard the error!
  • For brevity, I used transformers from a package I created, jzxhuang/http-extras, that exactly mirror the behaviour of elm/http. You could verify this yourself or implement it separately if you really wanted to.

This approach completely removes the dependency of having static files in our development or testing environment. Like the 2nd approach, we’re able to mock custom HTTP errors, but now we can also mock different methods! This is a big advantage as we don’t need to change all our methods to a GET request when we want tomock something.

Not so much an exploitation, as much as it is an example of the beauty of functional programming!

However, there is a new issue introduced with this approach, depending on the implementation. Since you now store a bunch of Responses in Elm, this increases the size of your application (the Responses won’t be removed by the compiler, even after optimization!). There are definitely ways to remedy this though — here’s two possible options:

  • Do some build-time magic that removes the mocking logic and Response files when building for a production environment. A little tedious, but certainly possible!
  • Structure the code differently so that you can pass the mocked Responses from in a manner so that it’s not included in the compiled code(Maybe as a Flag?).

This approach rectifies all the issues with our previous approaches, but there is still some certain behaviour we can’t replicate, like mocking a delayed response. It also has the possibility of increasing the size of your application, but overall, it’s a very powerful method that takes advantage of the fact that Elm is a functional language, and it isn’t too difficult to implement!

In addition to the transformers used in the example, I also have a full implementation and API for this approach in the package jzxhuang/http-extras. Check it out if you’re interested!

Approach #4: Mock Servers

The final approach is the most tedious and time-consuming to implement, but definitely the most robust. By using a mock server, you can mock any HTTP response or error you want, as well as things like sending a delayed response, specifying CORS behaviour, etc!

There’s plenty of mock servers available out there, and even some online services, so I’ll leave it to you to choose the one that suits your environment the best.

Summary

We’ve covered quite a bit in this guide, as HTTP mocking turns out to be a pretty complex topic. Here’s a summary of the different approaches:

A screenshot of a CSV table, because Medium doesn’t support tables for some reason. Source.

Even though Elm doesn’t have any built-in HTTP mocking, there’s still many ways you can mock your API calls! Hopefully this guide will help you in choosing the approach that works best for you.


Was this guide helpful? Or did I make a mockery of myself? If you any questions or comments, post away! Again, jzxhuang/http-extras has a full implementation of approach #3 as well as some other modules related to HTTP.

Thanks for reading!