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 useJason
orPoison
.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 theencode
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
- factory
Docs
smokestack
- factory
- after_build
- attribute
- before_build
:domain
(module that adoptsAsh.Domain
) - The default Ash Domain to use when evaluating loads
factory
Define factories for a resource
* after_build
* attribute
* before_build
:domain
(module that adoptsAsh.Domain
) - The Ash Domain to use when evaluating loads:resource
(module that adoptsAsh.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 ofatom/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 adoptsSpark.Dsl.Extension
) - A list of DSL extensions to add to theSpark.Dsl
:otp_app
(atom/0
) - The otp_app to use for any application configurable options:fragments
(list ofmodule/0
) - Fragments to include in theSpark.Dsl
. See the fragments guide for more.
Summary
Types
Choose a factory variant to use. Defaults to :default
.
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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 insert!(Ash.Resource.t(), [insert_option()]) :: Smokestack.RecordBuilder.result() | no_return()
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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 anencode/1
function which should return an ok/error tuple with the encoded result.
This is primarily for use withJason
orPoison
, 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 asdata
.
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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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()]) :: Smokestack.ParamBuilder.result() | no_return()
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 anencode/1
function which should return an ok/error tuple with the encoded result.
This is primarily for use withJason
orPoison
, 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 asdata
.
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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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 therelate
option.
For example:posts = insert!(Post, count: 3) author = insert(Author, relate: [posts: posts]) ``` The default value is `[]`.
- When building for a variant other than
:relate
(keyword/0
) - A list of records to relate.
For exampleauthor = 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