ryanzidago
Hi all,
In this article, I make the case for each test owning its setup.
Usually I forbid my AI agents to use the setup callbacks; I much prefer when tests are portable.
For example at work during code reviews, I often ask the AI agent to prove its finding by writing a failing test. Once the test is drafted, I ask it to publish it as a GitHub inline comment:
- I use the test as a tool to illustrate the agent’s point
- If the test assertions are valid, the author of the PR just needs to fix it
- the setup does not get in the way because the test is self-sufficient to be understood, you can just read the comment and get it, instead of trying to find the corresponding setup.
Even with huge setups, I prefer no common setup callbacks and have each test manually defininig their setup.
Let me know what you think!
Trending in Blog Posts
Hi everyone,
I’ve been building BetterTyping (https://bettertyping.org) on and off since 2021. It’s a free typing game: practice alone, ...
New
Turns out a 1ms network hiccup is enough to silently lose broadcasts for a node and that node is just quietly wrong forever
Wrote it up ...
New
If you’ve found an interesting or cool Elixir related blog post and think it’s worth sharing please add it to this thread :icon_biggrin: ...
New
The code in this post is Erlang, but it all applies to Elixir binaries just the same: how appends, match contexts and sub-binaries behave...
New
Semurg is a database for AI agent memory (a knowledge graph plus vectors, used over MCP) that we build in Elixir. This post walks one per...
New
eMudhra’s leaked CA keys reached my Phoenix app through 2 trust stores, and my first redeploy patched neither. How I fixed the OS bundle ...
New
I wrote up what it took to ship logs and metrics from two small Docker Swarm clusters into PostHog with the OpenTelemetry Collector: an a...
New
Other Trending Topics
Hey there,
I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first.
...
New
Hi everyone :waving_hand:
I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
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
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
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
(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
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #code-sync
- #discussion
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #ai
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #blog-post
- #elixirconf-us
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #security
- #elixirconf-eu
- #api
- #forms
- #hex











Showing Posts 1 to 7- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
nathanl
I strongly agree. If we want to DRY up duplication, we can do it like we do anywhere else, using functions.
test "does a thing" do%{stuff: stuff} = setup_the_stuff(%{which: :stuff})# test content hereendWith this approach, if I want to know what stuff was setup, I know to inspect the function(s) that the test called and/or the data they returned.
Another article that argues the same case:
ryanzidago
Ah yes! Strong article as well with strong arguments, especially with unused data and mystery guests!
Awlexus
Nitpick for the first example, I think you can completely side-step problem with the magic number by using the product price, like the test said it would
wrt to large, shared setup blocks like the one in your example
Elixir already provides tools that make it possible to balance the of setup blocks
Setup functions can be changed
Instead of writing one large block you can build composable setup blocks. for example
Then define the setup blocks
Using tags to modify setups per test
using
@tagwe can inject some values into the context ourselves, which we can use to modify shared setups to the needs of the test.Using these features the first test could be rewritten like this, allowing you to keep the tests (Not that I recommend this in particular, because using the product price directly would be preferred)
I do not disagree with the points presented, but I prefer the ability to compose and to extend when possible. Also dicipline your Agents early on with DRY or else you’ll find the same function 5 times in different places and with different names.
johantell
yes! I’ve been advocating for this for a long time and I’m happy that I managed to fully align my team around it early on
krasenyp
Good article but there’s an issue with the examples. Too much reliance on ExMachina. This can bite you pretty bad because inserts like this might not encode invariants which the business logic expects. Use your app’s API to prepare the data for your tests. If you can’t then you have a big problem.
ryanzidago
Interesting, and it might be worth another blog post!
I agree that
insert(:product)can potentially create a database-valid record that would be impossible to create through the real application.There is a tradeoff to using your application’s business logic for test setup:
I haven’t encountered enough factory-related problems to justify paying those costs in every test.
For focused tests, I still prefer simple ExMachina-style factories whose defaults represent valid domain state, while keeping the values relevant to the behavior explicit in the test.
Though I would definitely use the application’s business logic when the workflow or the invariants it establishes are part of what the test needs to prove / if it makes setting up the test scenario significantly easier:
fuelen
I argued the general case for this in another thread so I won’t repeat it.
I’d say mocks are a one-time cost with Mox rather than a per-test one. Global
stubin setup,expectonly in the tests that assert on the interaction.Side effects are real, and my answer is the same as in that thread. Skip the normal flow for the one step that’s too heavy, with a comment saying why.
I’d flip this around. The absence of it is the bigger cost. The schema grows an intermediate table, the real flow starts creating a membership, and
insert(:user, company: company)stays green. Nothing failed, so nothing told you that your setup now describes a state your application no longer produces. Setup that goes through your context functions either breaks loudly when the flow changes or picks up the change on its own.The “centralized setup layer built on functions” I recommended there is a library I maintain, I just didn’t name it at the time: seed_factory. It resolves dependent entities automatically, which is what makes going through business logic affordable.
Which brings me back to your original point. Rewriting ex_machina tests this way over the last three years, the setup often comes out shorter. I noticed that we mostly stopped moving setup into
setupblocks. My read is that people extract setup mainly once it gets long, so shared setup is partly a symptom of expensive setup.One day I’ll write the blog post about the road from ex_machina to seed_factory