In the past few weeks, you’ve seen how Elixir syntax makes a big difference when it’s time to write tests. In this article, we’re going to continue on that theme.

Business applications need business data, and tests for those applications need fake data. Make that data plausible and you’ll find debugging much easier. Creating plausible testing data is relatively simple, but often leads to repetitive, tedious code. That’s exactly the type of programming that my team works to avoid.

THE TYPICAL APPROACHES

Most typical test cases use one of a handful of approaches.

Explicit. An application can simply explicitly generate test data, leading to code that looks like this so:

The downside is that such code will get awkward once you are creating enough entities with enough attributes. In short order, the data overwhelms the test.

Through fixtures. An application can create test data in tabular form, and then load all of that data into the application.

This strategy works ok, but leads to code that is hard to maintain as applications grow because foreign keys are difficult to manage.

Through templates. An application can use some kind of template to create fake data. This is the approach we chose. We’ll show you many examples of this type of code as we go. This approach adds complexity, and the tradeoff is not worth it for simple applications. But once your application grows in complexity, the gains you can make in managing foreign keys and simplifying the creation of test data can be huge!

BEAUTIFUL DATA FOR BEAUTIFUL TESTS

If you’ve been following this series, you’ve seen first hand our strong desire to make the repetitive tasks we do beautiful. If I’m going to express a concept once or twice, I might be willing to live with something ugly or repetitive, but tests and the data that make them go will be expressed hundreds of times. I’m going to do everything I can to strip away the layers of tedium.

With Blacksmith, we want to be able to express these concepts with the best possible syntax:

  • Templates. The central premise of Blacksmith is that we express a template in a module called Forge. That template describes an entity. This foundation will feed the many different data forms that we need. Users can then override any of the map’s keys as needed.
  • Flexible back ends. An entity may be a map, a struct, or even a JSON map. A persistent store might be a CSV file, a relational database, or some other type of database.
  • Prototypes. Sometimes, one entity is based on another. For example, an employee may be a user with an employee number.
  • Saved and unsaved data. Sometimes, our data is database backed, and sometimes not.
  • Single items and collections. We want to be able to create a list just as easily as a single record.
  • Composite collections. Databases have primary and foreign keys, and keeping those aligned with test data is sometimes tedious.
  • Sequences to keep elements unique. Sometimes, a test scenario may need a unique email or identifier. Blacksmith makes it easy.

Let’s dig into each one of these elements in detail.

REGISTERING TEMPLATES AND PROTOTYPES WITH FORGE

You’ll register each data template in a module usually called Forge. This is a template for a Person. Notice the faker framework that we use to fake individual elements:

We’re registering each type of entity in our Forge module. The first entry registers users. The Faker library will supply plausible values for first_name and last_name. We’ll use a sequence to create an email so that each email for a new user will be unique. Blacksmith will register three different functions for creating users:

  • Forge.user( overrides \\ %{} ) will create a user, overriding any keys
  • Forge.user_list( n, overrides \\ %{}) will create a list of n users
  • Forge.saved_user( repo, overrides \\ %{}) will create a user, saved to repo
  • Forge.saved_user_list( n, repo, overrides \\ %{} ) will create a list of n users, saved to repo.

By default, calling user and user_list creates a map, though you can change this behavior. You’ll need to customize Blacksmith to use the saved_ versions.  We’ll show you how to override that behavior later, as well as how to use the override values.

Also in the above listing, you can see an admin registration. This is defined as a :blacksmith entry, which means the entry will take all of the default values from the specified :prototype and either overriding or copying the supplied values. For admins, Blacksmith will create a new user with a roles field set to [“admin”].

INSTANTIATING TEMPLATE ENTRIES

Now, you can create new users or admins. Just use your Forge module and the macros that Blacksmith creates for you, like this:

Or, you can create a list of five users, like this:

You can override any of the registered attributes, like this:

Simple and powerful. Now, your tests can quickly specify create new entities with many elements, but overriding or creating new attributes as needed.

MUTUAL ATTRIBUTES AND HAVING

You can also create several entities together with a mutual list of attributes. Let’s say we want to create a few users, all sharing the same country_id attribute. We could use the having macro, like this:

Now, both user and admin will have a country_id equal to usa_id.  This advantage is small here, but will grow significantly with the number of attributes and entities that you need to manage. For example, in our tests, we have many questions and choices, all which have the same survey_id and company_id.

What if we need to create some other type of entity, like say an Elixir struct?

CREATING STRUCTS

With a little knowledge of Elixir, you can easily have Blacksmith create structs for you, too. Since an Elixir struct is just a map with the extra __struct__ key, just adding the key

to the existing User record would create users that satisfy a struct called User.

CREATING CUSTOM ENTITIES

You can create something other than maps or structs. To do so, you just need to tell Blacksmith how. Elixir has a JSON library called Poison. Let’s create a forge that tells Blacksmith how to create a new JSON hash from an Elixir map, like this:

You can see a Forge with two specific enhancements. The first is the attribute variable, @new_function. Blacksmith creates maps, and that function tells Blacksmith how to create a JSON hash from a map.

Second, you can see the function new_json defined in Blacksmith.Config.  That function simply takes the attributes (the ones generated by Blacksmith), merges in the overrides, and then calls Poison.Encoder.Encode on the result. The result is that each new entity will create a new Json entity.

CUSTOMIZING PERSISTENCE

If you want to create persistent entities, you need to show Blacksmith how to save single entities, and lists of entities. For example, you might want to save records using Ecto, a persistence engine for Elixir. Here’s how you’d go about it. Let’s say you’re starting with an Ecto schema, like this:

We’re creating a simple database-backed entity. The database schema and model will both have corresponding fields.

Next, we’d create a forge. Our forge would specify the User struct for Ecto, as well as a couple of functions that Ecto could use to save one or many entities, like this:

Now, all that remains is to create a couple of functions to show Blacksmith how to persist Ecto data:

Now, if you have a repo called UserRepo, you can create one or many rows with

It’s that easy.

WRAPPING UP

If we had to create one or two database records for our tests, or if all of our entities had a handful of fields, we would probably opt for a different approach. We’d simply create them explicitly by hand. Similarly, if we had to create one database with complex relationships, we’d just use our software to create the database and save that data as fixtures.

Unfortunately, when you’re testing business software, neither of these conditions are true. We have to create plausible data for each one of the thousands of test cases we run. With this simple little strategy for creating flat entities and lists, saved or not, we can take the tedium out of running tests.