How do I write Elixir tests?

hauleth1 pts0 comments

How do I write Elixir tests? - HaulethHow do I write Elixir tests?<br>2026.05.13 :: 7 min :: #beam #testing<br>Table of content@subject module attribute for module under testdescribe with function nameAvoid module mockingAvoid ex_machina factoriesProperty testing is awesomeParting words

I created this post for myself to codify some basic guides that I use while writing tests. If you, my dear reader, want to read this, then remember one important thing:These are guides not rules. Each codebase is different and exceptions are expected and will happen. Just use the thing between your ears in your coding.@subject module attribute for module under test#<br>While reading ExUnit test, I often find it hard to remember which of the used modules is tested. Imagine test like:test "foo should frobnicate when bar" do<br>bar = pick_bar()

assert :ok == MyBehaviour.foo(MyImplementation, bar)<br>endIt is not obvious at the first sight what is tested here. And this is pretty simplified example. In real world it can became even harder to notice what is module under test (MUT).To resolve that I came up with a simple solution. I create module attribute named @subject that points to the MUT:@subject MyImplementation

test "foo should frobnicate when bar" do<br>bar = pick_bar()

assert :ok == MyBehaviour.foo(@subject, bar)<br>endNow it is more obvious what is MUT and what is just wrapper code around it.In the past I have been using alias with :as option, like:alias MyImplementation, as: SubjectHowever, I find module attribute to be more visually distinctive, which make it easier for me to notice @subject than Subject. But your mileage may vary.describe with function name#<br>That one is pretty basic. I have seen that it is pretty standard for people: when you are writing tests for module functions, then group them in describe blocks that will contain name (and arity) of the function in the name. Example:# Module under test<br>defmodule Foo do<br>def a(x, y, z) do<br># some code<br>end<br>end

# Tests<br>defmodule FooTest do<br>use ExUnit.Case, async: true

@subject Foo

describe "a/3" do<br># Some tests here<br>end<br>endThis allows me to see what functionality I am testing.Of course that doesn't apply to the Phoenix controllers, as there we do not test functions, but tuples in form {method, path} which I then write as METHOD path, for example POST /users. But the idea still stands - describe block provide immediate context about what is tested.Avoid module mocking#<br>In Elixir we have bunch of the mocking libraries out there, but most of them have quite substantial issue for me - these prevent me from using async: true for my tests. This often causes substantial performance hit, as it prevents different modules to run in parallel (not single tests, modules, but that is probably material for another post).Instead of mocks I prefer to utilise dependency injection. Some people may argue that "Elixir is FP, not OOP, there is no need for dependency injection". They could not be further from truth. DI isn't related to OOP, it just have different form - function arguments. For example, if we want to have function that do something with time, in particular - current time, then instead of writing:def my_function(a, b) do<br>do_foo(a, b, DateTime.utc_now())<br>endWhich would require me to use mocks for DateTime or other workarounds to make tests time-independent. I would do:def my_function(a, b, now \\ DateTime.utc_now()) do<br>do_foo(a, b, now)<br>endWhich still provide me the ergonomics of my_function/2 as above, but is way easier to test, as I can pass the date to the function itself. Now I can run this test in parallel as it will not cause other tests to do weird stuff because of altered DateTime behaviour.This approach I use a lot when I am writing some functions that are doing HTTP(S) requests to external services. I use optional1 keyword list argument called with super creative name opts. With that, I can pass option like :host which allows me to use tools like test_server which is great and it is, in my humble opinion, way better approach than any mocking.1Arguments in Elixir never are optional, it is a clever trick from language authors, but that is probably for another article.<br>Avoid ex_machina factories#<br>I have poor experience with tools like ex_machina or similar. These often bring whole Banana Gorilla Jungle problem back, just changed a little, as now instead of just passing data around, we create all needless structures for sole purpose of test, even when they aren't needed for anything.Start with example from ExMachina README:defmodule MyApp.Factory do<br># with Ecto<br>use ExMachina.Ecto, repo: MyApp.Repo

# without Ecto<br>use ExMachina

def user_factory do<br>%MyApp.User{<br>name: "Jane Smith",<br>email: sequence(:email, &"email-#{&1}@example.com"),<br>role: sequence(:role, ["admin", "user", "other"]),<br>end

def article_factory do<br>title = sequence(:title, &"Use ExMachina! (Part #{&1})")<br># derived attribute<br>slug = MyApp.Article.title_to_slug(title)<br>%MyApp.Article{<br>title: title,<br>slug: slug,<br># associations are...

tests module test subject elixir function

Related Articles