hauleth

hauleth

How do I write Elixir tests?

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.

Most Liked Responses

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.

Where Next?

Popular in Blog Posts Top

jordiee
https://medium.com/@jpiepkow/team-based-login-with-accesspass-be236d4bd7dd
New
ellispritchard
Somebody was asking about Erlang OS signal handling on Slack; I’ve posted up my learnings: https://medium.com/@ellispritchard/graceful-s...
New
paulanthonywilson
So you’re enjoying using WebSockets with Elixir’s Phoenix Framework, and you want to send some binary messages. Maybe it’s an audio clip,...
New
New
brainlid
In a 2 day spike, I created my own Elixir-based AI Personal Fitness Trainer! The surprising part for me was how useful and helpful I foun...
New
paulanthonywilson
This is an overview of different ways to try and kill an OTP process (in Elixir) and the behaviour to expect when that happens. I know th...
New
PragTob
I ran into an interesting problem recently where simple concurrency on the BEAM via Task.async made my application a lot slower and a lot...
New
paulanthonywilson
I had a bit of a mini-adventure following Sobelow’s advice on adding a CSP to a Phoenix App. If you want to follow along, or want to add ...
New
rlopzc
Use the new log handlers to plug Slack or any other provider into your logging system. https://rlopzc.com/posts/integrate-slack-into-the...
New
lawik
Building on other people’s work I bashed things together and suddenly I can know when someone is speaking using Elixir and Membrane.
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => "XXX...
New

We're in Beta

About us Mission Statement