Sharing fixtures between test modules in Elixir
This article was originally published on Medium.
Introduction
TL;DR If you came here only for the technical article, please jump to the next section. If you are curious about who am I and why I start publishing here, you can continue reading this introduction.
This is my first article on medium, so I thought it would be a good idea to start it with an introduction. I will present myself, who am I, what I do, and what you can expect to appear on this blog.
My name is Jean-Philippe Cugnet. I’m a 23 years old software engineer and photographer, living in Caen, France. I just got a Master degree in IT Security at the University of Caen and I’m currently working as an embedded software developer in a design house. I also love film photography a lot, making photos from little things people may not care about. The photography story goes on my website. The technical one continues here.
I came to Elixir six months ago—in March 2017—as a way to help me develop web applications. I knew PHP for a while, but wasn’t too good in web development. To really start doing great things, I had to choose something that would make the development process enjoyable. I thought for a long time about trying Ruby on Rails, but I’d read it was no longer a killer framework on the performance side. The hype is clearly towards Javascript these days, so I started to view fullstack JS and things like MEAN as a good option. But then I said to me front-end frameworks are a good option only for application that need Javascript. Not for rendering simple applications. At the same time, I read people on forums talking about Elixir scaling better than Javascript and being ready for concurrent programming. This was not in my needs, but it would be valuable anyway to learn something that is future-proof.
I run a server on which I installed eJabberd, an XMPP server written in Erlang. Curious about the language, I read its article on Wikipedia a while ago. I said to myself it would be fun to learn it some day. Then, in one of the eJabberd’s changelogs, I’ve seen something about Elixir. I did not particularly notice at the time, but reading again about it while looking for something new to learn did tilt. Then I came to Phoenix, and here am I.
I currently run one Phoenix application in “production” to help me organise parties with friends. I also played a bit with soft-realtime features provided by the Phoenix channels to remotely control an embedded system, and I’m now starting a new project to help film photographers manage their rolls of film, from stock to archives.
I’m starting this blog for sharing my solution to an issue I’ve already run into twice. I thought many times to start a blog to tell about different things. This time, I feel confident enough to start with this subject I really love: coding in Elixir. Many articles here will deal with software development, but I may also share my thoughts, ideas an knowledge about many other subjects. I may post often or not, I frankly don’t know. We’ll see.
The problem
A few days ago, I started to write kakte, my second Phoenix application with contexts. This is also the second time I’ve run into the same little problem: sharing fixtures across test modules. In fact, when you generate a new schema in Phoenix, you end up with close test fixtures in two different places.
First, you write the tests for the context with valid attributes:
defmodule Kakte.AccountsTest do
use Kakte.DataCase
alias Ecto.Changeset
alias Kakte.Accounts
describe "[users]" do
alias Kakte.Accounts.User
@password "U[.2)hkvL!6<"
@valid_attrs %{
username: "user",
email: "user@kakte.io",
password: @password,
password_confirmation: @password,
fullname: "John Smith",
}
@new_password "s0jH,Fst;HQm"
@update_attrs %{
username: "new_user",
email: "new_user@kakte.io",
password: @new_password,
password_confirmation: @new_password,
fullname: "Fred Smith",
}
@invalid_attrs %{
username: nil,
email: nil,
password: nil,
password_confirmation: nil,
fullname: nil,
}
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> Enum.into(@valid_attrs)
|> Accounts.register
user
end
test "list_users/0 returns all users" do
user = user_fixture()
assert Accounts.list_users == [user]
end
[...]
end
end
Then, you want to write some tests for the controller:
defmodule KakteWeb.UserControllerTest do
use KakteWeb.ConnCase
alias Kakte.Accounts
@password "U[.2)hkvL!6<"
@valid_attrs %{
username: "user",
email: "user@kakte.io",
password: @password,
password_confirmation: @password,
fullname: "John Smith",
}
@new_password "s0jH,Fst;HQm"
@update_attrs %{
username: "new_user",
email: "new_user@kakte.io",
password: @new_password,
password_confirmation: @new_password,
fullname: "Fred Smith",
}
@invalid_attrs %{
username: nil,
email: nil,
password: nil,
password_confirmation: nil,
fullname: nil,
}
def fixture(:user) do
{:ok, user} = Accounts.register(@create_attrs)
user
end
[...]
end
There is here a lot of code duplication! This is not great to write, and worse to maintain. What if I want to add an attribute to my users, like a role? I must update two files and that’s not really fun to do.
My solution
My solution to this problem has been to create a new module in
test/support/fixtures.ex
. It aims to provide fixtures for whatever schema you
want, just by using it:
defmodule Kakte.Fixtures do
@moduledoc """
A module for defining fixtures that can be used in tests.
This module can be used with a list of fixtures to apply as parameter:
use Kakte.Fixtures, [:user]
"""
def user do
alias Kakte.Accounts
quote do
@password "U[.2)hkvL!6<"
@valid_attrs %{
username: "user",
email: "user@kakte.io",
password: @password,
password_confirmation: @password,
fullname: "John Smith",
}
@new_password "s0jH,Fst;HQm"
@update_attrs %{
username: "new_user",
email: "new_user@kakte.io",
password: @new_password,
password_confirmation: @new_password,
fullname: "Fred Smith",
}
@invalid_attrs %{
username: nil,
email: nil,
password: nil,
password_confirmation: nil,
fullname: nil,
}
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> Enum.into(@valid_attrs)
|> Accounts.register
user
end
end
end
@doc """
Apply the `fixtures`.
"""
defmacro __using__(fixtures) when is_list(fixtures) do
for fixture <- fixtures, is_atom(fixture),
do: apply(__MODULE__, fixture, [])
end
end
Then, in my tests I am able to use them:
defmodule Kakte.AccountsTest do
use Kakte.DataCase
alias Ecto.Changeset
alias Kakte.Accounts
describe "[users]" do
use Kakte.Fixtures, [:user] # The fixtures for User are imported here
alias Kakte.Accounts.User
test "list_users/0 returns all users" do
user = user_fixture()
assert Accounts.list_users == [user]
end
[...]
end
end
If I need to import fixtures for several schemas in the future, I’ll be able to
do so by defining them in fixtures.ex
and passing the atoms as a list to the
use
statement:
use Kakte.Fixtures, [:user, :film_roll]
Conclusion
I hope this little article will be helpful to some of you in your search for writing better code. To Elixir developers with more experience: how do you solve this issue? Is there a better way to do it? Don’t hesitate to share your thoughts on this in the comments.