JEG2

JEG2

Author of Designing Elixir Systems with OTP

I’ve run into a few challenges so far with testing Elixir processes. I would appreciate any insight you can provide.

Scenario: Process Communication

We generally build GenServer processes in the form:

defmodule Whatever do
  # Client API

  def call_this(pid_or_name)
    GenServer.call(pid_or_name, …)
  end

  # Server API

  def handle_call(…)
    # ...
  end
end

Then let’s say I have code in another process that ends up doing something like:

defmodule Other do
  def handle_call(…)
    # …
    Whatever.call_this(pid_or_name)
  end
end

What’s the best way to test Other without also needing to work with Whatever or use mocking?

I can pass the module name to use into the process, but then I end up using apply() everywhere internally and building parallel test APIs for everything I need to stub at some point. I’m open to better ideas.

Scenario: Periodic Task Processes

Let’s say that I want a process to periodically do some task. If it’s a simple process, I could use Process.sleep/1 in the main loop or :timer.send_interval/2.

These are a pain to test, if the process manages this internally, but I’ve found that I can make a process respond to a message to trigger the task and eventually trigger that with :timer.send_interval/2 externally.

The move to GenServer makes this worse. Process.sleep/1 is out because I don’t control the loop. :timer.send_interval/2 means I need to construct the internal message format, which feels wrong.

My best idea is to add a process that periodically calls the correct interface function for me. (And I’m back to apply().) Are there better ways?

Scenario: Kick-starting Well Tested Processes

In solving some of these issues, I find myself facing a new challenge. I have well tested processes that just need to be wired up to each other, set to receive periodic messages, or whatever. Now the question becomes: where do I put that code?

In the OTP application structure, where do I put the little bit of “glue” or “matchmaking” code that just needs to run on startup?

Showing Posts 1 to 2

gregvaughn

gregvaughn

What I found in my first OTP learning app was the code telling me to push identification of collaborators into start_link calls and store them in a GenServer’s state. Those in turn the supervisor can read from config data in production. But in the test, you can start_link Other and pass in self as the pid to substitute in as a callback process. Then you can assert_receive or similar.

kujua

kujua

Author of Erlang and Elixir for Imperative Programmers

@JEG2 Can feel your pain.

Let me answer this from my perspective on a more general level:

(1) There is no rule that says have your handle_call in this function. Your scenario 1 calls the other server. What is the code before and after the call. What do you try to test? If it is just a call to Whatever than you need to test Whatever - perhaps having the code for handling the call in the module that can be tested easily.

(2) Mocking: It has its disadvantages, but should not be ruled out all the time. I have seen lots of abuse of mocking, but also useful case. Let’s assume that - again scenario 1 - the call to Whatever returns a critical state, then you would like to mock it to create different use cases - error, timeouts, different states. In my opinion, the call for some sort of dependency injection can lead to the same abuse than mocking.

(3) Kick-starting … Are the processes supervised? Then the code - I guess matchmaking means to pass an initial state - would be in the code that starts the children.

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews