Is this type of testing a GenServer idiomatic?

Hi everyone, I’ve been reading the PragProg ‘Programmin Elixir’ book, and have just completed Chapter 20 (building a simple file duplication app). I’ve got the code here: https://github.com/iarekk/elixir-duper/.

I’ve been really struggling with unit testing this project though. In my customary world (C#/.NET backend dev), a suite of unit tests would not be starting the application, but rather running individual method/class tests in isolation. Anything that talks to external file systems/DBs/services would be mocked, so unit tests run trivially on an air-gapped build machine.

This is not how Elixir seems to be operating.

The default behaviour is to start the full application when mix test is called. I like this approach as it’s immediately testing the supervision trees, and that all the modules are in place.

How is that supposed to work on a build machine though? Any real-world app will have external dependencies/services/secrets that are only available once you’re deployed to the correct env (dev/staging/prod), and shouldn’t be visible from the build agent.

Are my services supposed to detect they are on a build machine and fall back to some ‘mock’ implementations?

For now, I’ve managed to get my (pure) unit tests working by

I feel I’m fighting the ecosystem more than I’m using it at the moment. Is what I’m doing idiomatic to Elixir?

This is most often handled using configuration and application environments. Your comment about “detecting they are on a build machine and falling back to some mock implementation” is basically correct.

The basic pattern is to turn this:

ExternalThing.do_something()

into this:

external_thing().do_something()

def external_thing do
  # Fetch the :external_thing module from the app env, default to ExternalThing
  Application.get_env(:my_application, :external_thing, ExternalThing)
end

Here are a few resources I’d recommend:

  • The Application docs; particularly the section on application environments.
  • Config and Releases from the official Elixir Guides. This gets a bit into the weeds, but has some useful examples.
  • Mox and the Elixir School Mox lesson. Mox is a library that facilitates testing code that relies on external resources using behaviours.

Many popular libraries (Ecto, for instance, for databases) also have various built-in testing support in order to properly sandbox things.

1 Like

It is very common to call start_link for a specific GenServer for a individual test, and send messages to that GenServer to test its behavior. unlink is useful to test failure cases, which prevents the test from failing if the linked process crashes.

1 Like

This is massively helpful, thank you!

I think there’s great potential in building a test suite without the full supervision tree being started. I haven’t had enough influence early enough in a codebase yet to try it out, but I’d like to see that explored more and lessons shared. Yes, you’d be swimming upstream from most Elixir projects, but I suspect the way that influences your system design to be beneficial.