Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

Some time ago I manifested my dislike for using ETS tables because I saw them as Singletons, which bring a myriad of problems when testing. However @peerreynders linked me to the Just Create One pattern, spearheaded by our dear Uncle Bob.

Theoretical differences

I get the main difference. You can ask an app to create a Singleton a million times, and it will always return the same instance. With JCO, you can’t ask the app to create something a million times - you create it once and that’s it.

Practical differences

But in practice, specially when it comes to testing (which is what I am interested in), they are really both the same. You need a mock (mock as a noun) for each one, and once you bring either of them into your app, you basically forfeit concurrent tests because now you have a global state to worry about - which I dislike very much.

How do you test (or perhaps avoid?) something like a Singleton or a JCO instance and still allow for concurrent tests?

My only idea is to do functional injection, but then I have to pass a (huge) list of dependencies to every function. It is rather discouraging from a design perspective, as you literally need to pass around the whole jungle so you can tell a Gorilla to eat his freaking banana.

Discussion time

Any ideas on how to fix this, testing wise?

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

So there are a few considerations: Is the singleton stateful? Can it be mocked for the whole testsuite at once or do you need mocks per single test.

If the latter and it’s stateful then you’re probably out of luck. Even things like Ecto.Sandbox are quite powerful workarounds at best (without explicit ownership passing / injection of dependencies).

But I’m wondering why ETS brought this to the table, as ETS has other usecases as well. Named ETS tables with direct access are singletons, but that’s not all ETS can do.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

ETS and Registry are just 2 examples of Singletons (or JCOs) that are widely used in Elixir apps (as well as in my projects as well).

I am aware they may have other uses, but 90% we use them to store state as a singleton. This is the part I want to isolate and test … And if I am really out of luck, perhaps I should consider alternatives? ( the only alternative coming to mind is to save state in a GenServer process and test the inter-process communcation’s protocol but then I would be spawning processes for the sake of testing without any real runtime benefits and I don’t like that either).

LostKobrakai

LostKobrakai

I’m not sure how a GenServer would make it less of a singleton though? As I said it’s not really constraint to ETS. The best way around is imho not having a singleton in the first place, like with explicit dependency injection. Or look into some fancy workaround like Ecto.Sandbox does for Tasks in the newest version, which is basically an implicit version of ownership injection.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

With a GenServer you would not test the state itself, you would test the interactions with it. That’s the charm of interprocess protocol testing - you test if a given command was issued and that’s it.

The drawback here however, is that using GenServers has other types of drawbacks when compared to ETS tables (a discussion for another time).

LostKobrakai

LostKobrakai

You should be able to do the same with ETS though, if you hide it behind a functional interface and not use :ets directly all over the place.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

The problem is that with ETS (or Registry) you have to inject them directly into the functions that use them, thus we arrive at the whole jungle, gorilla and banana dilema.

LostKobrakai

LostKobrakai

I don’t really see how a GenServer would work differently here. Can you elaborate on that?

peerreynders

peerreynders

I don’t agree with this part. The idea behind Just Create One is to replace a global compile time dependency with a local one that is injected at runtime.

If you have 20 tests you can create:

  • 20 distinct ETS tables with distinct handles
  • 20 distinct ETS tables with distinct names
  • 20 distinct GenServers with distinct pids
  • 20 distinct GenServers with distinct names

i.e. one for each test so that you can run the tests concurrently.

That of course means that the process under test has to have the reference (handle/pid/name) injected during initialization and the reference has to be held in the process state. By extension any code using the reference will have it passed from the process state.

but then I have to pass a (huge) list of dependencies to every function.

how big are your functions that they need a (huge) list of dependencies??? This may be symptomatic of an entirely different organizational problem.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I would really like some tutorials or information about this. Testing code that uses ETS tables (or anything that uses them like Registry) is the bane of my existence in the company, as it makes unit tests quite hard to deal with and I hadn’t come up with a decoupling strategy that works good enough yet.

If you have 3 low level functions with side effects (HTTP request, DB operation, etc) and you have a function that is a composition of those 3, then in order to run the latter function you need to inject the dependencies of its pipeline.

Now imagine you combine this function with yet another function … The number of dependencies grows without bound.

You can say something among the lines of “perhaps you are testing the implementation instead of the contract”, but if I am only testing the functions those module’s APIs make public then I know this is not what is happening.

Another issue here is that for a system big enough, testing their public APIs is a nightmare because the input and the outputs generated are so big, that no one really understands what is going on and they end up just copy pasting whatever the system spit and use it as a test…

But I digress… my focus here is finding a good strategy that allows me to test code that uses ETS tables and Registries in an isolated concurrent and fast way.

LostKobrakai

LostKobrakai

Not really a tutorial, but still worth watching: Boundaries

Where Next? Top

Trending in Discussions Top

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
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
achempion
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
jtormey
Lately I’ve been thinking about how to organize components as a LiveView application grows. One of the pain points I’ve found (for myself...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
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
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
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews