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

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
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
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
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

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews