···7171[extra.twitter]
7272 site = "@hauleth"
7373 creator = "@hauleth"
7474+7575+[[extra.webrings]]
7676+ name = "Beambloggers"
7777+ url = "https://beambloggers.com/"
+290
content/post/writing-tests.md
···11++++
22+date = 2023-11-20
33+title = "How do I write Elixir tests?"
44+55+[taxonomies]
66+tags = [
77+ "elixir",
88+ "testing",
99+ "programming"
1010+]
1111++++
1212+1313+This post was created for myself to codify some basic guides that I use while
1414+writing tests. If you, my dear reader, read this then there is one important
1515+thing for you to remember:
1616+1717+These are **guides** not *rules*. Each code base is different deviations and are
1818+expected and *will* happen. Just use the thing between your ears.
1919+2020+## `@subject` module attribute for module under test
2121+2222+While writing test in Elixir it is not always obvious what we are testing. Just
2323+imagine test like:
2424+2525+```elixir
2626+test "foo should frobnicate when bar" do
2727+ bar = pick_bar()
2828+2929+ assert :ok == MyBehaviour.foo(MyImplementation, bar)
3030+end
3131+```
3232+3333+It is not obvious at the first sight what we are testing. And it is pretty
3434+simplified example, in real world it can became even harder to notice what is
3535+module under test (MUT).
3636+3737+To resolve that I came up with a simple solution. I create module attribute
3838+named `@subject` that points to the MUT:
3939+4040+```elixir
4141+@subject MyImplementation
4242+4343+test "foo should frobnicate when bar" do
4444+ bar = pick_bar()
4545+4646+ assert :ok == MyBehaviour.foo(@subject, bar)
4747+end
4848+```
4949+5050+Now it is more obvious what is MUT and what is just wrapper code around it.
5151+5252+In the past I have been using `alias` with `:as` option, like:
5353+5454+```elixir
5555+alias MyImplementation, as: Subject
5656+```
5757+5858+However I find module attribute to be more visually outstanding and make it
5959+easier for me to notice `@subject` than `Subject`. But your mileage may vary.
6060+6161+## `describe` with function name
6262+6363+That one is pretty basic, and I have seen that it is pretty standard for people:
6464+when you are writing tests for module functions, then group them in `describe`
6565+blocks that will contain name (and arity) of the function in the name. Example:
6666+6767+```elixir
6868+# Module under test
6969+defmodule Foo do
7070+ def a(x, y, z) do
7171+ # some code
7272+ end
7373+end
7474+7575+# Tests
7676+defmodule FooTest do
7777+ use ExUnit.Case, async: true
7878+7979+ @subject Foo
8080+8181+ describe "a/3" do
8282+ # Some tests here
8383+ end
8484+end
8585+```
8686+8787+This allows me to see what functionality I am testing.
8888+8989+Of course that doesn't apply to the Phoenix controllers, as there we do not test
9090+functions, but tuples in form `{method, path}` which I then write as `METHOD
9191+path`, for example `POST /users`.
9292+9393+## Avoid module mocking
9494+9595+In Elixir we have bunch of the mocking libraries out there, but most of them
9696+have quite substantial issue for me - these prevent me from using `async: true`
9797+for my tests. This often causes substantial performance hit, as it prevents
9898+different modules to run in parallel (not single tests, *modules*, but that is
9999+probably material for another post).
100100+101101+Instead of mocks I prefer to utilise dependency injection. Some people may argue
102102+that "Elixir is FP, not OOP, there is no need for DI" and they cannot be further
103103+from truth. DI isn't related to OOP, it just have different form, called
104104+function arguments. For example, if we want to have function that do something
105105+with time, in particular - current time. Then instead of writing:
106106+107107+```elixir
108108+def my_function(a, b) do
109109+ do_foo(a, b, DateTime.utc_now())
110110+end
111111+```
112112+113113+Which would require me to use mocks for `DateTime` or other workarounds to make
114114+tests time-independent. I would do:
115115+116116+```elixir
117117+def my_function(a, b, now \\ DateTime.utc_now()) do
118118+ do_foo(a, b, now)
119119+end
120120+```
121121+122122+Which still provide me the ergonomics of `my_function/2` as above, but is way
123123+easier to test, as I can pass the date to the function itself. This allows me to
124124+run this test in parallel as it will not cause other tests to do weird stuff
125125+because of altered `DateTime` behaviour.
126126+127127+## Avoid `ex_machina` factories
128128+129129+I have poor experience with tools like `ex_machina` or similar. These often
130130+bring whole [Banana Gorilla Jungle problem][bgj] back, just changed a little, as
131131+now instead of just passing data around, we create all needless structures for
132132+sole purpose of test, even when they aren't needed for anything.
133133+134134+[bgj]: https://softwareengineering.stackexchange.com/q/368797
135135+136136+Start with example from [ExMachina README](https://github.com/beam-community/ex_machina#overview):
137137+138138+```elixir
139139+defmodule MyApp.Factory do
140140+ # with Ecto
141141+ use ExMachina.Ecto, repo: MyApp.Repo
142142+143143+ # without Ecto
144144+ use ExMachina
145145+146146+ def user_factory do
147147+ %MyApp.User{
148148+ name: "Jane Smith",
149149+ email: sequence(:email, &"email-#{&1}@example.com"),
150150+ role: sequence(:role, ["admin", "user", "other"]),
151151+ }
152152+ end
153153+154154+ def article_factory do
155155+ title = sequence(:title, &"Use ExMachina! (Part #{&1})")
156156+ # derived attribute
157157+ slug = MyApp.Article.title_to_slug(title)
158158+ %MyApp.Article{
159159+ title: title,
160160+ slug: slug,
161161+ # associations are inserted when you call `insert`
162162+ author: build(:user),
163163+ }
164164+ end
165165+166166+ # derived factory
167167+ def featured_article_factory do
168168+ struct!(
169169+ article_factory(),
170170+ %{
171171+ featured: true,
172172+ }
173173+ )
174174+ end
175175+176176+ def comment_factory do
177177+ %MyApp.Comment{
178178+ text: "It's great!",
179179+ article: build(:article),
180180+ author: build(:user) # That line is added by me
181181+ }
182182+ end
183183+end
184184+```
185185+186186+For start we can see a single problem there - we do not validate our factories
187187+against our schema changesets. Without additional tests like:
188188+189189+```elixir
190190+@subject MyApp.Article
191191+192192+test "factory conforms to changeset" do
193193+ changeset = @subject.changeset(%@subject{}, params_for(:article))
194194+195195+ assert changeset.valid?
196196+end
197197+```
198198+199199+We cannot be sure that our tests test what we want them to test. And if we pass
200200+custom attribute values in some tests it gets even worse, because we cannot be
201201+sure if these are conforming either.
202202+203203+That mean that our tests may be moot, because we aren't testing against real
204204+situations, but against some predefined state.
205205+206206+Another problem is that if we need to alter the behaviour of the factory it can
207207+became quite convoluted. Imagine situation when we want to test if comments by
208208+author of the post have some special behaviour (for example it has some
209209+additional CSS class to be able to mark them in CSS). That require from us to do
210210+some dancing around passing custom attributes:
211211+212212+```elixir
213213+test "comments by author are special" do
214214+ post = insert(:post)
215215+ comment = insert(:comment, post: post, author: post.author)
216216+217217+ # rest of the test
218218+end
219219+```
220220+221221+And this is simplified example. In the past I needed to deal with situations
222222+where I was creating a lot of data to pass through custom attributes to make
223223+test sensible.
224224+225225+Instead I prefer to do stuff directly in code. Instead of relying on some
226226+"magical" functions provided by some "magical" macros from external library I
227227+can use what I already have - functions in my application.
228228+229229+Instead of:
230230+231231+```elixir
232232+test "comments by author are special" do
233233+ post = insert(:post)
234234+ comment = insert(:comment, post: post, author: post.author)
235235+236236+ # rest of the test
237237+end
238238+```
239239+240240+Write:
241241+242242+```elixir
243243+test "comments by author are special" do
244244+ author = MyApp.Users.create(%{
245245+ name: "John Doe",
246246+ email: "john@example.com"
247247+ })
248248+ post = MyApp.Blog.create_article(%{
249249+ author: author,
250250+ content: "Foo bar",
251251+ title: "Foo bar"
252252+ })
253253+ comment = MyApp.Blog.create_comment_for(article, %{
254254+ author: author,
255255+ content: "Foo bar"
256256+ })
257257+258258+ # rest of the test
259259+end
260260+```
261261+262262+It may be a little bit more verbose, but it makes tests way more readable in my
263263+opinion. You have all details just in place and you know what to expect. And if
264264+you need some piece of data in all (or almost all) tests within
265265+module/`describe` block, then you can always can use `setup/1` blocks. Or you
266266+can create function per module that will generate data for you. As long as your
267267+test module is self-contained and do not receive "magical" data out of thin air,
268268+it is ok for me. But `ex_machina` is, in my opinion, terrible idea brought from
269269+Rails world, that make little to no sense in Elixir.
270270+271271+If you really need such factories, then just write your own functions that will
272272+use your contexts instead of relying on another library. For example:
273273+274274+```elixir
275275+import ExUnit.Assertions
276276+277277+def create_user(name, email \\ nil, attrs \\ %{}) do
278278+ email = email || "#{String.replace(name, " ", ".")}@example.com"
279279+ attrs = Map.merge(attrs, %{name: name, email: email})
280280+281281+ assert {:ok, user} = MyApp.Users.create(attrs)
282282+283283+ user
284284+end
285285+286286+# And so on…
287287+```
288288+289289+This way you do not need to check if all tests use correct validations any
290290+longer, as your system will do that for you.