View Source Myrmidex (myrmidex v0.3.0)

A light wrapper around StreamData. Generate any data on the fly, or model the underlying types and common fields of domain-specific structs or schemas, optionally using custom generators. Aimed at speeding up test setup, and maximizing reusability of factory data throughout testing and development.

Uses reflection and composition to provide sensible defaults and allow for maximum reusability in test setup, db or cache seeding, performance testing, or just general-purpose development utility.

Quick example

Generate representative data from any term:

iex> "🐜"
...> |> Myrmidex.many()
...> |> Enum.join(" ")
"🐩 🐰 🐡 🐂 🐏 🐁 🐋 🐤 🐪 🐭 🐏 🐨 🐋 🐁 🐚 🐤"

Optionally, constrain or altogether prevent generation:

iex> "🐜"
...> |> Myrmidex.fix()
...> |> Myrmidex.many()
...> |> Enum.join(" ")
"🐜 🐜 🐜 🐜 🐜 🐜 🐜 🐜 🐜 🐜"

iex> %{species: species_generator()}
...> |> Myrmidex.affix(kingdom: "Animalia", class: "Insecta")
...> |> Myrmidex.many()
[
  %{
    kingdom: "Animalia",
    class: "Insecta",
    species: "🪰"
  },
  %{
    kingdom: "Animalia",
    class: "Insecta",
    species: "🐞"
  },
  ...
]

More useful examples

Say you're working with an api, and have defined the a schema for validation purposes:

defmodule MyApp.StripeApi.Responses.BalanceTransaction do
  use Ecto.Schema

  @primary_key {:id, :binary_id, autogenerate: true}
  embedded_schema do
    field :object, Ecto.Enum, values: [:balance_transaction]
    field :amount, :integer
    field :available_on, :utc_datetime
    field :created, :utc_datetime
    field :currency, :string
    ...
  end

end

Quickly develop in the REPL, or write tests against this schema using autogenerated stream data:

iex> Mymidex.one(%BalanceTransaction{})
%MyApp.StripeApi.Responses.BalanceTransaction{
  id: "2b134329-a292-4643-b251-cf107083f6ec",
  object: :balance_transaction,
  amount: 29,
  available_on: ~U[2015-01-18 22:40:28Z],
  created: ~U[2037-10-08 16:49:27Z],
  currency: "0RuOTBTOP4fYL"
}

The currency field string generation isn't particularly useful. Let's pin that value using affix/2 and then generate a list of attrs with string keys we can use to test changeset functions:

iex> %BalanceTransaction{}
...> |> Myrmidex.affix(currency: "usd")
...> |> Myrmidex.many(attr_keys: :string)
[
  %{
    "id" => "7d8979b6-e99b-48d3-8acb-862866f6630a",
    "amount" => -51,
    "currency" => "usd",
    ...
  },
  %{
    "id" => "9be66e53-58e2-405a-9c7a-4ae6b536e3ce",
    "amount" => 37,
    "currency" => "usd",
    ...
  },
  ...
]

Helpful, but we might want to customize the binary id generation, or handle other specifics of this api. We can do this with a Myrmidex.GeneratorSchema.

Ideally, generator schemas are broad enough to cover segments of the schemas and/or structs in our application or domain, reducing the repetition of ad hoc test-data definitions throughout the codebase.

If we're setting up tests or using this regularly, or we need generated fields to build upon or be constrained by one another (e.g. the two date fields in the example), we'd potentially want to go one step further and setup up a Myrmidex.Factory.

Factories are also the place to define a relationship of a schema or set of schemas to a datastore.

Options

Factories and genrators accept the following common set of opts:

  • :generator_schema - The default value is Myrmidex.GeneratorSchemas.Default.

  • :limit_generation? (boolean/0) - Constant generators in fixed map generation unless generators are affixed. The default value is false.

  • :attr_keys (:string or :atom) - Force the type of generated attr keys for maps and structs.

  • :drop_autogenerate? (boolean/0) - Include autogenerated ids and other fields for EctoSchemas. The default value is false.

  • :drop_associations? (boolean/0) - Include association fields for EctoSchemas. The default value is false.

  • :list_opts (keyword/0) - StreamData options for list generators. The default value is [max_length: 30].

Summary

Functions

Given term and a compatible term of overrides, map values via StreamData.constant/1 to override any derivation of values via reflection or pattern matching.

Same as affix/2 but affixes a list of term.

Wrap any term except %StreamData{} in StreamData.constant/1.

Build a stream of term, and then emit a list of size count of representative data. Like many/2, may also accept a range.

Build a stream of term and then emit a random representative. Generally useful with maps or structs, but can be used with any term.

The main entry point to working with stream data. Produces stream data from term, implementing inferred stream data types for fields.

Lazily transform the results of a stream via a function. I.e. just a shorthand for StreamData.repeatedly/1 wrapped by StreamData.bind/2.

Functions

Given term and a compatible term of overrides, map values via StreamData.constant/1 to override any derivation of values via reflection or pattern matching.

Will not override previously defined generators.

Examples

iex> alias Myrmidex.Support.Fixtures.TestPumpkin
iex> humfrey = Myrmidex.affix(%TestPumpkin{}, name: "Humfrey", eats: "🎃")
iex> humfreys = Myrmidex.many(humfrey, 2)
iex> match?([%TestPumpkin{name: "Humfrey", eats: "🎃"}, %TestPumpkin{name: "Humfrey"}], humfreys)
true
Link to this function

affix_many(term, overrides)

View Source
Link to this function

affix_many(term, count, overrides)

View Source

Same as affix/2 but affixes a list of term.

Wrap any term except %StreamData{} in StreamData.constant/1.

Examples

iex> stream = Myrmidex.fix("🐜")
iex> Myrmidex.many(stream, 3)
["🐜", "🐜", "🐜"]
iex> match?(^stream, Myrmidex.fix(stream))
true
Link to this function

many(term, count \\ %{__struct__: Range, first: 2, last: 20, step: 1}, opts \\ [])

View Source

Build a stream of term, and then emit a list of size count of representative data. Like many/2, may also accept a range.

Because of the call to StreamData.resize/2, this function prevents narrowing in favor of more randomly representative data.

Examples

iex> alias Myrmidex.Support.DocsGeneratorSchema
iex> animoji = Myrmidex.many("🐜", 2..5, generator_schema: DocsGeneratorSchema)
iex> ascii_codes = Enum.flat_map(animoji, & String.to_charlist(&1))
iex> Enum.all?(ascii_codes, & &1 in 128000..128048)
true

Build a stream of term and then emit a random representative. Generally useful with maps or structs, but can be used with any term.

Examples

iex> alias Myrmidex.Support.DocsGeneratorSchema
iex> animoji = Myrmidex.one("🐜", generator_schema: DocsGeneratorSchema)
iex> [ascii_code] = String.to_charlist(animoji)
iex> ascii_code in 128000..128048
true
Link to this function

to_stream(term, opts \\ [])

View Source

The main entry point to working with stream data. Produces stream data from term, implementing inferred stream data types for fields.

Examples

iex> alias Myrmidex.Support.DocsGeneratorSchema
iex> animojis = Myrmidex.to_stream("🐜", generator_schema: DocsGeneratorSchema)
iex> animoji = Myrmidex.one(animojis)
iex> [ascii_code] = String.to_charlist(animoji)
iex> ascii_code in 128000..128048
true
Link to this function

via(stream_data, via_fun)

View Source

Lazily transform the results of a stream via a function. I.e. just a shorthand for StreamData.repeatedly/1 wrapped by StreamData.bind/2.