Back

creating predictable seed data

A challenge appears when there's a requirement to create a lot of unique seed data. Traditionally the developer community would reach for randomized libraries such as Chance while generating data such as a new user with a random email, name, gender etc. This can lead to hard to automation test code as you cannot guarantee what a user's data will look like other than it's generic shape. One common solution is to write static test fixtures which come with their own problems such as difficulty scaling, updating, and interdependencies.

Preview of a table of repeating yet unique and predictable content

Instead, what if we tied randomness to a static value such as an index within a loop (this can also be done with static seeds). In the following examples we'll utilize a simple loop to create various types of predictable data.

@def create_transaction do
  count_to_generate = 150
  amount_multiplier = 100

Enum.map(1..count_to_generate, fn index -> # Example creating a money transaction App.Transactions.create_transaction(%{ amount: &1 * amount_multiplier, } end) ) end

# Creates transactions with amounts tied to the index modified by some amount # Ex: [%{amount: 100}, %{amount: 200}... %{amount: 15,000}]

But sure, you're saying this may only work for numbers. Well we can get clever and leverage looping over lists as well with this helper function...

@doc """
  Allows for being given a list, then passing the current index
    to loop over the results like A, B, C, A, B, C...
  ## Examples
      iex> repeating_loop_through_list(["A", "B", "C"], 1)
      "B"
      iex> repeating_loop_through_list(["A", "B", "C"], 3)
      "A"
  """
  @spec repeating_loop_through_list(list(), integer()) :: any()
  def repeating_loop_through_list(list, index),
    do: Enum.at(list, Integer.mod(index, Enum.count(list)))

With the above function we now can tie indexes to a list of repeating options.

currencies = ["USD", "RUB", "JPY"]
Enum.map(1..count_to_generate, fn index ->
	App.Transactions.create_transaction(%{
      currency: repeating_loop_through_list(currencies, index),
    })
end)
# Ex: [%{currency: "USD"}, %{currency: "RUB"}... %{currency: "USD"}]

Another fun trick is to leverage string padding to create example ID's or "codes" that may be tied to each item you're generating. I find this particularly useful while testing sorting.

code_length = 3
padding_character = "0"
Enum.map(1..count_to_generate, fn index ->
	App.Transactions.create_transaction(%{
      code: String.pad_leading(to_string(index), code_length, padding_character),
    })
end)
# Ex: [%{code: "001"}, %{code: "002"}... %{code: "150"}]

This works for dates as well!

date_year = 2023
date_month = 01
max_date_day = 30
Enum.map(1..count_to_generate, fn index ->
	App.Transactions.create_transaction(%{
      sent_date: Date.new!(date_year, date_month, min(index, max_date_day)),
    })
end)
# Ex: [%{date: ~D[2023-01-01]}, %{date: ~D[2023-01-02]}]

At the end of the day, this is just example test data. Good is better than perfect and so long as you're shipping something that looks and feels like the final product you're doing yourself, other developers, and QA a service.

Back