How to mock functions from Elixir built-in modules

I’m still in a learning phase regarding testing in Elixir, and yesterday I was trying to understand the common patterns to mock built-in modules

I have the following module + function:

defmodule FizzBuzz do
  def build(file_name) do
    file_name
    |> File.read()
    |> process_file_result
  end

And in the test, I’d like to mock the File.read result in order to not have to read from my filesystem:

 describe("build/1") do
    test "when a valid file is provided, returns the converted list" do
      expected_response = {:ok, [2, :buzz, :buzz, :fizzbuzz, :buzz, :buzz]} 
      assert FizzBuzz.build("numbers.txt") == expected_response
    end

I searched for a couple of libraries and found Mox, can it be used for that purpose?

1 Like

You could use the mock library

1 Like

Yes a very common pattern is to create your own API for “file reading functions”, use that instead of File directly, and then you can mock your API in tests as covered in this post

3 Likes

I’m probably missing some nuance due to the example being generic, but it sounds like what you want is a test of process_file_result, not one for build:

  • you want to set an input string for it during test setup
  • you want to inspect the output

No mocks needed in that case…

5 Likes

Even though it’s the opposite of what you asked, if you ever find yourself wanting to test against the file system, ExUnit has a handy tmp_dir solution.

You can definitely use Mox, though. I’ve also heard good things about Patch if you want a possibly more familiar option (depending on what ecosystem you’re used to).

2 Likes

Yeah, that makes sense

process_file_result already defines some guards depending on the File.read result, with that said, I could also only test that function without needing to rely on build

defp process_file_result({:error, reason}),
    do: {:error, "Error while reading the file: #{reason}"}

  defp process_file_result({:ok, content}) do

The only implementation detail there is that process_file_result is a private function, and build is the only one public, but I do understand your point!

1 Like

Thanks for all the references! If I could ask one more thing, what are the use cases for tmp_dir in production-level codebases?

Also, Patch seems pretty cool! More similar to what I’m used to in the TypeScript world

1 Like

It’s good for scenarios where the main purpose is to write to disk and you want to test reality without mocks. I do top-down TDD and I write very little code that writes to disk, so I feel better about taking the small hit on testing speed and testing what’s actually going to happen as opposed to bring in mocks. YMMV, of course, I don’t work on particularly massive systems or anything. I’m also using it for an image generation service where I want to write out the result and compare it to the expected image not just programmatically but visually as well.

2 Likes

Everyone before me gave good suggestions. I wouldn’t use mocks for this test. I also generally prefer stubs. Mocks are very tricky because they test interaction. In your case this is the call to File.read. If you at some point, hypothetically, use a different function for reading, your test will be broken.

2 Likes

This is not necessarily true of Mox at least, it’s part of the “mocks as nouns” rule. By creating your own mock API that is used locally (in the specific logic you want to mock) rather than globally you create more intentional boundaries that are less fragile.

1 Like

Could you clarify on stubs here? Stubs for me in the Node.js ecosystem are when HTTP requests are intercepted

1 Like

There’s also Mimic — Mimic v1.7.4. I haven’t tried it yet though.

2 Likes

Generally, a mocked function comes with expectations which form a specification of how the function is going to be called. Stubs, on the other hand, only provide predefined answers to calls and, in my experience, doesn’t lead to testing calling logic which shouldn’t matter most of the times.

3 Likes

Your build/1 function is begging for an integration test because it “speaks to the outside world” (even though it’s the local file system.)

You could consider using fixture files, where one is valid and one is invalid, and you could assert accordingly.

Even though this post is about Ruby, I like the “Don’t mock what you don’t own” philosophy and I’ve happily used it in Elixir. I’d argue that you don’t “own” File.

Another option would be to inject the File.read/1 dependency, so your function could change as such:

- def build(file_name) do
+ def build(file_name, opts \\ []) do
+ reader = opts[:reader] || &File.read/1
  file_name
- |> File.read()
+ |> reader.()
  |> process_file_result()
end

(I just hand-wrote that diff above, please excuse any errors)

Sometimes I use DI because it’s more convenient, but I think in this specific example I’d personally opt for the fixture files.

2 Likes

I’ve found that it’s occasionally useful to move a private function that I want to test to an ‘internal module’, e.g. FizzBuzz.FileProcessing. That seems to be pretty clear on its own but you can, of course, also make it explicit with a suitable module-doc:

defmodule FizzBuzz.FileProcessing
  @moduledoc """
  This is an internal module for `FizzBuzz`.
  """

  ...

I’ve also found that it’s often fine – good even – to just use a test file, i.e. “read from my filesystem”.

2 Likes

By convention @moduledoc false modules are to be considered private.

5 Likes