hauleth

hauleth

Guidance Counsellor

I wanted to write down some of the main guides I use when writing Elixir tests, so I have summarised them in blogpost.

Furthermore, I think that mocking must be destroyed.

First Post!

johns10davenport

johns10davenport

Destroyed and replaced with cassettes, recorded from actual calls. I’d love to see a generic recorder for “any” elixir module/function call and response to etf or similar format. This has only become more important as use of llms grows.

Most Liked

krasenyp

krasenyp

Man, don’t get me started on mocking. I’ve been looking at poor designs for years and the only way to test anything at all is by using mocks. You talk about DI in relation to mocking, which is great, but I also miss programming against a contract which can be achieved through protocols.

tfwright

tfwright

Agree with describe with function name but I also include the specific input data case (with foo and bar) because I prefer to test those in isolation to avoid cascading failures and ballooning setup.

I have tried strictly using contexts for setup and just find it too cumbersome, although I grant it seems more pure and I also avoid libraries for struct generation that “compete” with contexts. I write whatever code is most convenient for the case. Sometimes that’s a context function, mostly it’s a private helper calling insert!. Have yet to be bitten by this.

I am on team “don’t write application code for the sake of tests” so I just mock stuff. It’s a tradeoff, but I’d rather my code running in prod as as univocal and expressive as possible, even if that costs some cycles. But that’s from writing code for 25 years before AI came along, I find some of my personal “guidelines” bending quite a bit recently.

fuelen

fuelen

I would ask the opposite question. Do you want a test suite that checks your code in states the system can actually reach in production, paying for it with some slower setup, or do you want fast tests that run on data no business flow could actually produce? “Just write a valid setup by hand” is easy to say. So is “just write bug free code”. If either was realistic, we would not need tests in the first place :slight_smile:

Context functions carry a contract, and for anything non trivial that contract is much simpler than putting rows into tables by hand. Let’s say you write a chat and you want a thread with a first message. The function is something like start_thread(sender, recipient, initial_message). Internally it creates the thread, links both users, inserts the message, marks it read for sender and unread for recipient. One line in your test setup vs. a lot of ceremony to recreate that exact state directly in the DB.

So is it really “more complex”? I’d say writing it is less complex. There is more code running, yes. But that code is exactly what reduces the space of valid fixture states down to what the system can actually produce. To me that is the whole point, not a cost.

Heavy, yes, in absolute terms. But ex_machina is not free either, and I think people underestimate this. :org has one :user and has many :project, and :project has :created_by and :approved_by assocs, both pointing at :user. Declare those naively as associations and a plain insert(:project) will create three users where you wanted one. To prevent that you start writing factory logic that walks through the other associations to reuse an existing user, and the simple user: build(:user) one liner turns into something messy inside the factory file. The complexity does not disappear.

My personal take: have a centralized test setup layer built on functions, and by default let it call context functions. When a specific context function is too heavy and is used in most tests (for example, users are created as :pending and must be activated through a separate flow, which is correct for the business but annoying as a prerequisite for every test), I add a lighter setup helper for that case, with a short comment explaining why it skips the normal flow.

Last Post!

johns10davenport

johns10davenport

Mocks are brittle if calling an external service. Less so if you’re calling your own functions, but then you can just call your own functions. Definitely my least used test artifact at this point.

DI is preferable. I have an in memory file system fake I inject into an environment abstraction in my main app. It’s fine.

I really prefer to use the cassette approach at the boundary though. It’s the most reliable and least brittle paradigm imo.

Where Next? Top

Trending in Blog Posts Top

bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
pckrishnadas88
Hey everyone! :waving_hand: I’ve published Part 7 of the Building Distributed Systems in Elixir series, where we build core distributed ...
New
mudasobwa
So, instead of wasting my afternoon arguing with anonymous handles on X, I turned to my trusty, soulless assistant and said: “Listen, ple...
New
zorn
An educational side project in Elixir, Phoenix, and Tauri. I share what I learned while wiring Automerge into the BEAM, including how I s...
New
abreujp
New article: Elixir Project Structure — From mix new to a Growing Codebase I’ve published a new article in my Elixir learning series on d...
New
zorn
:pencil: Phoenix’s built-in LiveView test helpers require you to hand-build the form payload and start a fresh pipe after every click. Wo...
New
jola
Wrote about how to safely run a globally unique process in an Elixir cluster, and a scary story from the past! Learn about :global for r...
New

Other Trending Topics Top

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
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews