Elixir Testing

Generating Test Data in Phoenix application using ExMachina & Faker

The purpose behind factories usage in testing and how to use ExMachina & Faker to set up test data in an Elixir Phoenix project.

Long Nguyen
Nimble

--

When jumping from the Ruby/Rails world to Elixir & Phoenix, one may wonder how to generate test data?

In a Ruby on Rails application, we typically use external dependencies, called gems, such as Fabricator and FactoryBot. Luckily, there is a similar approach in Elixir. We can use hex packages such as ExMachina. Let’s find out how we can use it.

First things first, why use factories?

When writing tests, setting up test data is always needed. As an example, let’s assume that we have a User schema with three attributes: name, email and birthday.

We could write a test like the following:

Test data is generated by hardcoding the user_attrs map and using Repo.insert.

For other test cases, when there is no need to insert data into the database, Repo.insert can be omitted. Assertions are thus against the changeset.

For both types of test, this approach has the following drawbacks:

  • Irrelevant test data: in the test above, we only care about the user email.
    However, due to the validation rules (both the name and email attributes are required), we need to specify the name attribute in the test data. That gets tedious when we have many validations and constraints; we need to specify the attributes to fulfill those rules, even though the test has nothing to do with those attributes.
  • Coupling: imagine if we need to change a validation rule.
    For example, in our case, let’s assume the birthday attribute is now required. We would then need to update all tests to follow that change.

How about generating just the right amount of information — only the data that impacts the expectation in the setup and none of the irrelevant data included?

As you may already guess, test factories are introduced to achieve that very purpose.

In fact, factories are not a test-specific concept. It is a design pattern that comes in handy for testing. Similar to a real-world factory, a test factory is responsible for “manufacturing” (in this case) data — hence the term. The idea is that factories provide convenient methods to define groups of data with default values that can create a valid record. In the tests, we can then explicitly override the attributes that are pertinent to the test.

For example, in our test case above, we can generate the test data by only overriding the email attribute, leaving irrelevant data out of the scope. For example

user = insert(:user, email: "johndoe@example.com")

That sounds good, so tell me, how I can use Factories in Elixir?

In the Elixir world, ExMachina is a popular test factory generator. To use it in a Phoenix project, as always, it first needs to be added as a dependency:

Next, add this line to test/test_helper.exs before ExUnit.start:

Then let’s create a file at test/support/factory.ex to define our first factory:

We can now generate a test user in our tests in different ways:

The official doc provides much more information.

With these simple factory methods, we can generate only the relevant data in our test. So our test can now be updated to:

No more irrelevant data 🎉

Even if our validation constraints change, for example, the birthday attribute is required, we can simply update the user_factory method to add that attribute without changing the test data across every test.

Organizing factories

If we keep defining more factory methods in that test/support/factory.ex file, it can quickly get large and hard to manage. To get over that, we can split the factories into multiple files.

Let’s start with our UserFactory.

That piece of code may look a bit complicated so let’s find out what it does.

First, we split all of our user factory methods into a new UserFactory module. We then declare that UserFactory in the main MyApp.Factory module on this line:

It means the line use MyApp.UserFactory will require the UserFactory module and then call the macro named __using__ of that module. That's the behavior of the use macro (read more in the official Elixir doc)

Inside the __using__ macro, we define several factory methods inside the quote. quote returns the underlying representation of Elixir code. By doing all of the above steps, it injects the factory methods into the current context.

All good? We now have well-organized factories to generate test data. But can we go a step further? You may notice that our test data is not unique:

The generated users always have the same name. We can work around this issue by generating a random string for the attribute name, but that would get tedious if we have more attributes to care for, each of them may require a different way to generate random data. How about a more elegant solution to generate the value for each attribute? That's where Faker comes into play.

A short introduction to Faker

Faker is a pure Elixir library for generating fake data.
source

As usual, we need to define it as a dependency before using it:

Then it can be used in the factories:

No changes in the way we use our factories:

It looks way more elegant and consistent, right? The generated data also appears to be more real.

Check out the Faker doc for more in-depth guides & examples.

Wrapping up

Testing is beneficial, we all know that, but many may be unwilling to do testing due to the tedious tasks of setting up test data. With the right tools, we can alleviate that pain and maybe, make it a joy. But as with many tools, use test factories wisely. It exists to solve specific problems.

💡 If you want a simpler solution to define test factories, without relying on a third party like ExMachina, we can just use Ecto to implement such methods. Read more about Ecto’s Test factories.

Originally published at https://nimblehq.co.

--

--