View Source Smokestack behaviour (smokestack v0.9.1)

Smokestack provides a way to define test factories for your Ash Resources using a convenient DSL:

defmodule MyApp.Factory do
  use Smokestack

  factory Character do
    attribute :name, &Faker.StarWars.character/0
    attribute :affiliation, choose(["Galactic Empire", "Rebel Alliance"])
  end
end

defmodule MyApp.CharacterTest do
  use MyApp.DataCase
  use MyApp.Factory

  test "it can build a character" do
    assert character = insert!(Character)
  end
end

Templates

Each attribute uses a template to generate a value when building a factory. Templates can be anything that implements the Smokestack.Template protocol. This protocol is automatically implemented for functions with arities zero through two - meaning you can just drop in your own functions - or use one of the built-in helpers from Smokestack.Dsl.Template.

Variants

Sometimes you need to make slightly different factories to build a resource in a specific state for your test scenario.

Here's an example defining an alternate :trek variant for the character factory defined above:

factory Character, :trek do
  attribute :name, choose(["J.L. Pipes", "Severn", "Slickback"])
  attribute :affiliation, choose(["Entrepreneur", "Voyager"])
end

Building resource records

You can use insert/2 and insert!/2 to build and insert records. Records are inserted using Ash.Seed.seed!/2, which means that they bypass the usual Ash lifecycle (actions, validations, etc).

Options

  • load: an atom, list of atoms or keyword list of the same listing relationships, calculations and aggregates that should be loaded after the record is created.
  • count: rather than inserting just a single record, you can specify a number of records to be inserted. A list of records will be returned.
  • build: an atom, list of atoms or keyword list of the same describing relationships which you would like built alongside the record. If the related resource has a variant which matches the current one, it will be used, and if not the :default variant will be.
  • attrs: A map or keyword list of attributes you would like to set directly on the created record, rather than using the value provided by the factory.
  • relate: A keyword list of relationships to records (or lists of records) to which you wish to directly relate the created record.

Building parameters

As well as inserting records directly you can use params/2 and params!/2 to build parameters for use testing controller actions, HTTP requests, etc.

Options

  • encode: rather than returning a map or maps, provide an encoder module to serialise the parameters. Commonly you would use Jason or Poison.
  • nest: rather than returning a map or maps directly, wrap the result in an outer map using the provided key.
  • key_case: change the case of the keys into one of the many cases supported by recase.
  • key_type: specify whether the returned map or maps should use string or atom keys (ignored when using the encode option).
  • count: rather than returning just a single map, you can specify a number of results to be returned. A list of maps will be returned.
  • build: an atom, list of atoms or keyword list of the same describing relationships which you would like built within the result. If the related resource has a variant which matches the current one, it will be used, and if not the :default variant will be.
  • attrs: A map or keyword list of attributes you would like to set directly on the result, rather than using the value provided by the factory.

DSL Documentation

Index

  • smokestack
    • factory
      • after_build
      • attribute
      • before_build

Docs

smokestack

  • factory
    • after_build
    • attribute
    • before_build

  • :domain (module that adopts Ash.Domain) - The default Ash Domain to use when evaluating loads

factory

Define factories for a resource

* after_build
* attribute
* before_build
  • :domain (module that adopts Ash.Domain) - The Ash Domain to use when evaluating loads

  • :resource (module that adopts Ash.Resource) - Required. An Ash Resource

  • :variant (atom/0) - The name of a factory variant The default value is :default.

  • :auto_build (one or a list of atom/0) - A list of relationships that should always be built when building this factory The default value is [].

  • :auto_load - An Ash "load statement" to always apply when building this factory The default value is [].

after_build

Modify the record after building.

Allows you to provide a function which can modify the built record before returning.

These hooks are only applied when building records and not parameters.

  • :hook (mfa or function of arity 1) - Required. A function which returns an updated record
attribute
  • :name (atom/0) - Required. The name of the target attribute

  • :generator - Required. A function which can generate an appropriate value for the attribute.œ

before_build

Modify the attributes before building.

Allows you to provide a function which can modify the the attributes before building.

  • :hook (mfa or function of arity 1) - Required. A function which returns an updated record

Options

  • :extensions (list of module that adopts Spark.Dsl.Extension) - A list of DSL extensions to add to the Spark.Dsl

  • :otp_app (atom/0) - The otp_app to use for any application configurable options

  • :fragments (list of module/0) - Fragments to include in the Spark.Dsl. See the fragments guide for more.

Summary

Types

t()

Choose a factory variant to use. Defaults to :default.

Callbacks

Runs a factory and uses it to insert Ash resources into their data layers.

Raising version of insert/2.

Runs a factory and uses it to build a parameters suitable for simulating a request.

Raising version of params/2.

Types

@type insert_option() :: variant_option() | Smokestack.RecordBuilder.option()
@type param_option() :: variant_option() | Smokestack.ParamBuilder.option()
@type recursive_atom_list() :: atom() | [atom() | {atom(), recursive_atom_list()}]
@type t() :: module()
@type variant_option() :: {:variant, atom()}

Choose a factory variant to use. Defaults to :default.

Callbacks

@callback insert(Ash.Resource.t(), [insert_option()]) ::
  {:ok, Smokestack.RecordBuilder.result()} | {:error, any()}

Runs a factory and uses it to insert Ash resources into their data layers.

Automatically implemented by modules which use Smokestack.

Options

  • :load - An optional Ash load statement.
    You can request any calculations, aggregates or relationships you would like loaded on the returned record.
    For example:

    insert!(Post, load: [:full_title])
    assert is_binary(post.full_title)
    ``` The default value is `[]`.
  • :count - Specify the number of instances to build.
    Use this option to run the factory a number of times and return the results as a list.
    For example:

    posts = params!(Post, count: 3)
    assert length(posts) == 3
    ``` The default value is `nil`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.

Options for building multiple instances

Options for building relationships

Raising version of insert/2.

Automatically implemented by modules which use Smokestack.

Options

  • :load - An optional Ash load statement.
    You can request any calculations, aggregates or relationships you would like loaded on the returned record.
    For example:

    insert!(Post, load: [:full_title])
    assert is_binary(post.full_title)
    ``` The default value is `[]`.
  • :count - Specify the number of instances to build.
    Use this option to run the factory a number of times and return the results as a list.
    For example:

    posts = params!(Post, count: 3)
    assert length(posts) == 3
    ``` The default value is `nil`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.

Options for building multiple instances

Options for building relationships

@callback params(Ash.Resource.t(), [param_option()]) ::
  {:ok, Smokestack.ParamBuilder.result()} | {:error, any()}

Runs a factory and uses it to build a parameters suitable for simulating a request.

Automatically implemented by modules which use Smokestack.

Options

  • :encode - Provide an encoder module.
    If provided the result will be passed to an encode/1 function which should return an ok/error tuple with the encoded result.
    This is primarily for use with Jason or Poison, however you may wish to define your own encoder for your tests.
    For example:

    post = params!(Post, encoder: Jason)
    assert Jason.decode!(post)
  • :nest - Nest the result within an arbitrary map key.
    Mostly provided as a shorthand for building API requests where the built instance may need to be nested inside a key such as data.
    Takes the result and nests it in a map under the provided key.
    For example:

    request = params!(Post, nest: :data)
    assert is_binary(request.data.title)

    If you need a more complicated mangling of the result, I suggest using the encode option.

  • :key_case - Change the key case.
    Sometimes you will need to change the case of the keys before sending to an API. Behind the scenes we use recase to remap the keys as specified.
    For example:

    iex> params!(Post, key_case: :kebab) |> Map.keys()
    [:title, :"sub-title"] The default value is `:snake`.
  • :key_type - Specify string or atom keys.
    Allows you to specify the type of the returned keys.
    For example:

    iex> params!(Post, key_type: :string) |> Map.keys()
    ["title", "sub_title"]

    Valid values are :string, :atom The default value is :atom.

  • :count - Specify the number of instances to build.
    Use this option to run the factory a number of times and return the results as a list.
    For example:

    posts = params!(Post, count: 3)
    assert length(posts) == 3
    ``` The default value is `nil`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.

Options for building multiple instances

Options for building relationships

Raising version of params/2.

Automatically implemented by modules which use Smokestack.

Options

  • :encode - Provide an encoder module.
    If provided the result will be passed to an encode/1 function which should return an ok/error tuple with the encoded result.
    This is primarily for use with Jason or Poison, however you may wish to define your own encoder for your tests.
    For example:

    post = params!(Post, encoder: Jason)
    assert Jason.decode!(post)
  • :nest - Nest the result within an arbitrary map key.
    Mostly provided as a shorthand for building API requests where the built instance may need to be nested inside a key such as data.
    Takes the result and nests it in a map under the provided key.
    For example:

    request = params!(Post, nest: :data)
    assert is_binary(request.data.title)

    If you need a more complicated mangling of the result, I suggest using the encode option.

  • :key_case - Change the key case.
    Sometimes you will need to change the case of the keys before sending to an API. Behind the scenes we use recase to remap the keys as specified.
    For example:

    iex> params!(Post, key_case: :kebab) |> Map.keys()
    [:title, :"sub-title"] The default value is `:snake`.
  • :key_type - Specify string or atom keys.
    Allows you to specify the type of the returned keys.
    For example:

    iex> params!(Post, key_type: :string) |> Map.keys()
    ["title", "sub_title"]

    Valid values are :string, :atom The default value is :atom.

  • :count - Specify the number of instances to build.
    Use this option to run the factory a number of times and return the results as a list.
    For example:

    posts = params!(Post, count: 3)
    assert length(posts) == 3
    ``` The default value is `nil`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.
  • :build - A (nested) list of relationships to build.
    A (possibly nested) list of Ash resource relationships which is traversed building any instances as needed.
    For example:

    post = insert!(Post, build: Author)
    assert is_struct(post.author, Author)

    Caveats:

    • When building for a variant other than :default a matching variant factory will be looked for and used if present, otherwise it will build the default variant instead.
    • Note that for relationships whose cardinality is "many" we only build one instance.
      If these caveats are an issue, then you can build them yourself and pass them in using the relate option.
      For example:
      posts = insert!(Post, count: 3)
      author = insert(Author, relate: [posts: posts])
      ``` The default value is `[]`.
  • :relate (keyword/0) - A list of records to relate.
    For example

    author = insert!(Author)
    post = insert!(Post, relate: [author: Author])
    ``` The default value is `[]`.
  • :attrs (map/0) - Attribute overrides.
    You can directly specify any overrides you would like set on the resulting record without running their normal generator.
    For example:

    post = params!(Post, attrs: %{title: "What's wrong with Huntly?"})
    assert post.title == "What's wrong with Huntly?"
    ``` The default value is `%{}`.

Options for building multiple instances

Options for building relationships