├── .gitignore ├── .tool-versions ├── .travis.yml ├── README.md ├── config ├── config.exs └── test.exs ├── lib ├── ecto_factory.ex └── ecto_factory │ └── exceptions.ex ├── logos ├── ectofactory_logo.png ├── ectofactory_logo.svg ├── ectofactory_logo_bw.png ├── ectofactory_logo_bw.svg ├── ectofactory_logo_text.png ├── ectofactory_logo_text.svg ├── ectofactory_logo_text_bw.png └── ectofactory_logo_text_bw.svg ├── mix.exs ├── mix.lock ├── sketch.txt └── test ├── ecto_factory_test.exs ├── support ├── blog_post.ex ├── ecto_uri.ex ├── repo.ex ├── role.ex └── user.ex └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | /doc 5 | erl_crash.dump 6 | *.ez 7 | /.elixir_ls 8 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | elixir 1.14.2-otp-25 2 | erlang 25.1.2 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.9.1 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Easily generate data based on your ecto schemas. 2 | 3 | [Hex docs homepage](https://hexdocs.pm/ecto_factory/api-reference.html) 4 | 5 | ## Installation 6 | 7 | Add ecto_factory to your list of dependencies in `mix.exs`: 8 | 9 | ```elixir 10 | def deps do 11 | [ 12 | {:ecto_factory, "~> 0.1.0"} 13 | ] 14 | end 15 | ``` 16 | 17 | # Usage 18 | 19 | Assume we're using the following `User` module: 20 | 21 | ```elixir 22 | defmodule User do 23 | use Ecto.Schema 24 | 25 | schema "users" do 26 | field :age, :integer 27 | field :username, :string 28 | field :date_of_birth, :date 29 | field :address, :map 30 | end 31 | end 32 | 33 | ``` 34 | 35 | Now you can use EctoFactory to generate a Ecto 36 | struct with random data for each of your differnt 37 | fields 38 | 39 | ```elixir 40 | EctoFactory.schema(User) 41 | 42 | #=> %User{ 43 | age: 230419832, #random number for an :integer 44 | username: aduasdaitquweofads, #random string for a :string 45 | date_of_birth: ~D[2019-10-10], #today for a :date 46 | address: %{ #random map of string for a :map 47 | "aduiasdoufp" => "eruiqweu" 48 | "wfhsddha" => "uudfadsuyanbasvasd" 49 | } 50 | } 51 | ``` 52 | 53 | You can set defaults in your config: 54 | 55 | 56 | ```elixir 57 | # config/config.exs 58 | 59 | config :ecto_factory, factories: [ 60 | user_with_defaults: { User, [ 61 | age: 99, 62 | username: "mrmicahcooper", 63 | date_of_birth: Date.from_iso8601!("2012-12-12") 64 | ] } 65 | ] 66 | ``` 67 | 68 | Which will allow you to do the following: 69 | 70 | ```elixir 71 | 72 | EctoFactory.schema(:user_with_defaults) 73 | 74 | #=> %User{ 75 | age: 99 76 | username: "mrmicahcooper" 77 | date_of_birth: ~D[2012-12-12] 78 | address: %{ 79 | "aduiasdoufp" => "eruiqweu" 80 | "wfhsddha" => "uudfadsuyanbasvasd" 81 | } 82 | } 83 | 84 | ``` 85 | 86 | Notice the `:address` is still random data 87 | 88 | And if you don't want to use random data or defaults, 89 | you can specify your attributes at runtime: 90 | 91 | ```elixir 92 | EctoFactory.schema(User, 93 | username: "foo", 94 | age: 2, 95 | date_of_birth: ~D[2019-01-01], 96 | address: %{}, 97 | ) 98 | 99 | #=> %User{ 100 | address: %{} 101 | age: 2, 102 | date_of_birth: ~d[2019-01-01], 103 | username: "foo", 104 | } 105 | ``` 106 | 107 | You can also use EctoFactory's own generators to 108 | create radom data. You can do this with `build`. 109 | 110 | ```elixir 111 | EctoFactory.build(User, 112 | username: :string, # random string 113 | email: :email, # random email 114 | address: {:map, :integer}, # map where the keys are random strings, and the values are random integers 115 | ) 116 | 117 | #=> %{ 118 | address: %{ 119 | aduiasdoufp => 12387128412, 120 | wfhsaaddha => 1238194012 121 | }, 122 | age: 1293812931, 123 | date_of_birth: ~d[2019-01-01], 124 | username: "asdlfkjad", 125 | } 126 | 127 | ``` 128 | When using `build` the keys of your map will be atoms 129 | 130 | You can also use EctoFactory's own generators to 131 | create radom data. You can do this when specifying 132 | attributes: 133 | 134 | ```elixir 135 | EctoFactory.attrs(User, 136 | username: :string, # random string 137 | email: :email, # random email 138 | address: {:map, :integer}, # map where the keys are random strings, and the values are random integers 139 | ) 140 | 141 | #=> %{ 142 | "address" => %{ 143 | "aduiasdoufp" => 12387128412, 144 | "wfhsaaddha" => 1238194012 145 | }, 146 | "age" => 1293812931, 147 | "date_of_birth" => ~d[2019-01-01], 148 | "username" => "asdlfkjad", 149 | } 150 | 151 | ``` 152 | When using `attrs` the keys of your map will be strings 153 | 154 | You can also just call these straight from 155 | EctoFactory with `&Ectofactory.gen/1`: 156 | 157 | ```elixir 158 | EctoFactory.gen(:id) 159 | # => 1204918243915 # randome integer 160 | 161 | EctoFactory.gen(:binary_id) 162 | # => "7D654105-F75D-4E73-8B97-BC7CE1E51EDA" # uuid 163 | 164 | EctoFactory.gen(:email) 165 | # => "yjlsbxdcyqhcwswwhv@ogrlycqeycqezslb.omuaqbhpqjmqtnbifoa" #email composed of a bunch of random strings 166 | ``` 167 | 168 | Here is a full list of the random things you can 169 | create. Notice this is a 1-1 mapping of Ecto's 170 | default data types with 1 (more to come) 171 | convenience type - `:email` 172 | 173 | ``` 174 | :id 175 | :binary_id 176 | :integer 177 | :float 178 | :decimal 179 | :boolean 180 | :binary 181 | :date 182 | :time 183 | :time_usec 184 | :naive_datetime 185 | :naive_datetime_usec 186 | :utc_datetime 187 | :utc_datetime_usec 188 | :string 189 | :array 190 | {:array, type} 191 | :map 192 | {:map, type} 193 | :email 194 | ``` 195 | 196 | ## Development 197 | 198 | ``` 199 | $ git clone https://github.com/mrmicahcooper/ecto_factory 200 | $ cd ecto_factory 201 | $ mix deps.get 202 | $ mix test 203 | ``` 204 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | if File.exists?("./config/#{config_env()}.exs") do 4 | import_config "#{config_env()}.exs" 5 | end 6 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :ecto_factory, repo: EctoFactory.Repo 4 | 5 | config :ecto_factory, 6 | factories: [ 7 | user: User, 8 | default_user: {User}, 9 | user_with_default_username: {User, username: "mrmicahcooper"} 10 | ] 11 | -------------------------------------------------------------------------------- /lib/ecto_factory.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoFactory do 2 | @moduledoc """ 3 | EctoFactory is a super easy way to generate fake data for an Ecto Schema. 4 | It requires zero setup and generates random data based on the fields you've defined. 5 | """ 6 | 7 | import Enum, only: [random: 1] 8 | import Application, only: [get_env: 2] 9 | 10 | @doc """ 11 | Create a struct of the passed in factory 12 | 13 | After configuring a factory 14 | 15 | config :ecto_factory, factories: [ 16 | user: User, 17 | 18 | user_with_default_username: { User, 19 | username: "mrmicahcooper" 20 | } 21 | ] 22 | 23 | You can build a struct with the attributes from your factory as defaults. 24 | 25 | EctoFactory.schema(:user_with_default_username) 26 | 27 | %User{ 28 | age: 124309132# random number 29 | username: "mrmicahcooper" 30 | } 31 | 32 | And you can pass in your own attributes of course: 33 | 34 | 35 | EctoFactory.schema(:user, age: 99, username: "hashrocket") 36 | 37 | %User{ 38 | age: 99, 39 | username: "hashrocket" 40 | } 41 | 42 | """ 43 | @spec schema(atom() | Ecto.Schema, keyword() | map()) :: Ecto.Schema 44 | def schema(factory_name, attrs \\ []) do 45 | {schema, attributes} = build_attrs(factory_name, attrs) 46 | struct(schema, attributes) 47 | end 48 | 49 | @doc """ 50 | Create a map with randomly generated of the passed in Ecto schema. The keys of this map are `atoms` 51 | """ 52 | @spec build(atom() | Ecto.Schema, keyword() | map()) :: map() 53 | def build(factory_name, attrs \\ []) do 54 | build_attrs(factory_name, attrs) 55 | |> elem(1) 56 | |> Enum.into(%{}) 57 | end 58 | 59 | @doc """ 60 | Create a map with randomly generated of the passed in Ecto schema. The keys of this map are `String`s 61 | """ 62 | @spec attrs(atom() | Ecto.Schema, keyword() | map()) :: map() 63 | def attrs(factory_name, attrs \\ []) do 64 | build_attrs(factory_name, attrs) 65 | |> elem(1) 66 | |> Enum.into(%{}, fn {k, v} -> {to_string(k), v} end) 67 | end 68 | 69 | # Generators for the standard ecto types 70 | @doc """ 71 | Generate a random value. `gen/1` will return a value of the atom or tuple to pass it. 72 | """ 73 | @spec gen(atom() | tuple()) :: 74 | integer() 75 | | binary() 76 | | float() 77 | | boolean() 78 | | list() 79 | | Time 80 | | NaiveDateTime 81 | | Date 82 | | DateTime 83 | 84 | def gen(:id), do: random(1..9_999_999) 85 | def gen(:binary_id), do: Ecto.UUID.generate() 86 | def gen(:integer), do: random(1..9_999_999) 87 | def gen(:float), do: (random(99..99999) / random(1..99)) |> Float.round(2) 88 | def gen(:decimal), do: gen(:float) 89 | def gen(:boolean), do: random([true, false]) 90 | def gen(:binary), do: Ecto.UUID.generate() 91 | def gen(:date), do: Date.utc_today() 92 | def gen(:time), do: Time.utc_now() 93 | def gen(:time_usec), do: gen(:time) 94 | 95 | def gen(:naive_datetime) do 96 | NaiveDateTime.utc_now() 97 | |> NaiveDateTime.truncate(:second) 98 | end 99 | 100 | def gen(:naive_datetime_usec), do: gen(:naive_datetime) 101 | def gen(:utc_datetime), do: DateTime.utc_now() 102 | def gen(:utc_datetime_usec), do: gen(:utc_datetime) 103 | def gen(:string), do: random_string(20) 104 | 105 | def gen(:array), do: gen({:array, :string}) 106 | def gen({:array, type}), do: 1..random(2..9) |> Enum.map(fn _ -> gen(type) end) 107 | 108 | def gen(:map), do: gen({:map, :string}) 109 | 110 | def gen({:map, type}) do 111 | for(key <- gen({:array, :string}), into: %{}, do: {key, gen(type)}) 112 | end 113 | 114 | # Special generators for helpful things 115 | def gen(:email), do: "#{gen(:string)}@#{gen(:string)}.#{gen(:string)}" 116 | 117 | def gen(:url) do 118 | host = random_string(3..6) <> "." <> random_string(4..8) 119 | tld = ~w[com co.uk ninja ca biz org gov software io us rome.it ai] |> random() 120 | "https://#{host}.#{tld}" 121 | end 122 | 123 | # Module names are atoms 124 | # When the schema passes in a module, ask it for it's Ecto type 125 | # 126 | # schema "users" do 127 | # field(:balance, EctoURI) 128 | # end 129 | # 130 | # Ecto Types must identify themselves with a type() function 131 | # https://hexdocs.pm/ecto/Ecto.Type.html 132 | def gen(ecto_type_module) when is_atom(ecto_type_module), do: gen(ecto_type_module.type()) 133 | 134 | # fallback to nil - this should probably raise 135 | def gen(_), do: nil 136 | 137 | defp build_attrs(factory_name, attributes) do 138 | Code.ensure_loaded(factory_name) 139 | {schema, defaults} = 140 | if function_exported?(factory_name, :__changeset__, 0) do 141 | {factory_name, []} 142 | else 143 | factory(factory_name) 144 | end 145 | 146 | attributes = Enum.into(attributes, []) 147 | 148 | non_generated_keys = 149 | defaults 150 | |> Kernel.++(attributes) 151 | |> Keyword.keys() 152 | |> Kernel.++(schema.__schema__(:primary_key)) 153 | 154 | attrs = 155 | schema.__changeset__() 156 | |> Map.drop(non_generated_keys) 157 | |> Enum.map(&cast/1) 158 | |> Keyword.merge(defaults) 159 | |> Keyword.merge(attributes) 160 | |> Enum.map(&gen_attribute/1) 161 | 162 | {schema, attrs} 163 | end 164 | 165 | defp gen_attribute({key, value}) when is_atom(value) and value not in [true, false, nil], 166 | do: {key, gen(value)} 167 | 168 | defp gen_attribute({key, {_, _} = value}), do: {key, gen(value)} 169 | defp gen_attribute(key_value), do: key_value 170 | 171 | defp cast({key, {:assoc, %{cardinality: :many}}}), do: {key, nil} 172 | defp cast({key, {:assoc, %{cardinality: :one}}}), do: {key, nil} 173 | defp cast({key, data_type}), do: {key, gen(data_type)} 174 | 175 | defp factory(factory_name) do 176 | case get_env(:ecto_factory, :factories)[factory_name] do 177 | nil -> raise(EctoFactory.MissingFactory, factory_name) 178 | {schema, defaults} -> {schema, defaults} 179 | {schema} -> {schema, []} 180 | end 181 | end 182 | 183 | defp random_string(min..max) do 184 | for(_ <- 1..random(min..max), into: "", do: <>) 185 | end 186 | 187 | defp random_string(max_length) do 188 | for(_ <- 1..random(8..max_length), into: "", do: <>) 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/ecto_factory/exceptions.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoFactory.MissingFactory do 2 | @moduledoc """ 3 | Raised at runtime when the factory is not defined. 4 | """ 5 | defexception [:message] 6 | 7 | def exception(factory_name) do 8 | helper_text = """ 9 | Could not find factory by `:#{factory_name}`. 10 | Define it in your configuration: 11 | 12 | config :ecto_factory, factories: [ 13 | #{factory_name}: Myapp.EctoModule 14 | ] 15 | """ 16 | 17 | %__MODULE__{message: helper_text} 18 | end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /logos/ectofactory_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmicahcooper/ecto_factory/9e2b7b2f856aa9c8387feb959bd9ce5098148c82/logos/ectofactory_logo.png -------------------------------------------------------------------------------- /logos/ectofactory_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ectofactory 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /logos/ectofactory_logo_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmicahcooper/ecto_factory/9e2b7b2f856aa9c8387feb959bd9ce5098148c82/logos/ectofactory_logo_bw.png -------------------------------------------------------------------------------- /logos/ectofactory_logo_bw.svg: -------------------------------------------------------------------------------- 1 | 2 | ectofactory 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /logos/ectofactory_logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmicahcooper/ecto_factory/9e2b7b2f856aa9c8387feb959bd9ce5098148c82/logos/ectofactory_logo_text.png -------------------------------------------------------------------------------- /logos/ectofactory_logo_text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ectofactory 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /logos/ectofactory_logo_text_bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmicahcooper/ecto_factory/9e2b7b2f856aa9c8387feb959bd9ce5098148c82/logos/ectofactory_logo_text_bw.png -------------------------------------------------------------------------------- /logos/ectofactory_logo_text_bw.svg: -------------------------------------------------------------------------------- 1 | 2 | ectofactory 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule EctoFactory.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ecto_factory, 7 | version: "0.3.1", 8 | elixir: "~> 1.13", 9 | build_embedded: Mix.env() == :prod, 10 | start_permanent: Mix.env() == :prod, 11 | name: "EctoFactory", 12 | source_url: "https://github.com/mrmicahcooper/ecto_factory", 13 | package: package(), 14 | description: description(), 15 | deps: deps(), 16 | docs: [ 17 | main: "readme", 18 | logo: "./logos/ectofactory_logo.png", 19 | extras: [ 20 | "README.md" 21 | ] 22 | ] 23 | ] 24 | end 25 | 26 | def application do 27 | [applications: [:logger, :ecto]] 28 | end 29 | 30 | defp deps do 31 | [ 32 | {:ecto, "~> 3.0"}, 33 | {:ex_doc, "~> 0.21", only: :dev}, 34 | {:earmark, "~> 1.4", only: :dev} 35 | ] 36 | end 37 | 38 | defp description do 39 | """ 40 | Easily generate structs and maps based on your ecto schemas. 41 | """ 42 | end 43 | 44 | defp package do 45 | [ 46 | name: :ecto_factory, 47 | licenses: ["Apache-2.0"], 48 | maintainers: ["Micah Cooper"], 49 | links: %{ 50 | "GitHub" => "https://github.com/mrmicahcooper/ecto_factory" 51 | } 52 | ] 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, 3 | "earmark": {:hex, :earmark, "1.4.34", "d7f89d3bbd7567a0bffc465e0a949f8f8dcbe43909c3acf96f4761a302cea10c", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "90b106f3dad85b133b10d7d628167c88246123fd1cecb4557d83d21ec9e65504"}, 4 | "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, 5 | "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, 6 | "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, 7 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, 8 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, 9 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 10 | "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, 11 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, 12 | "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, 13 | } 14 | -------------------------------------------------------------------------------- /sketch.txt: -------------------------------------------------------------------------------- 1 | Ecto Types to randomly generate 2 | 3 | # Ecto type | Elixir type | Literal syntax in query 4 | -# :id | integer | 1, 2, 3 5 | - # :binary_id | binary | <> 6 | - # :integer | integer | 1, 2, 3 7 | - # :float | float | 1.0, 2.0, 3.0 8 | - # :boolean | boolean | true, false 9 | - # :string | UTF-8 encoded string | "hello" 10 | - # :binary | binary | <> 11 | - # {:array, inner_type} | list | [value, value, value, ...] 12 | - # :map | map 13 | - # {:map, inner_type} | map 14 | - # :decimal | Decimal 15 | - # :date | Date 16 | - # :time | Time 17 | - # :time_usec | Time 18 | - # :naive_datetime | NaiveDateTime 19 | - # :naive_datetime_usec | NaiveDateTime 20 | # :utc_datetime | DateTime 21 | # :utc_datetime_usec | DateTime 22 | -------------------------------------------------------------------------------- /test/ecto_factory_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EctoFactoryTest do 2 | use ExUnit.Case, async: true 3 | 4 | Code.require_file("test/support/ecto_uri.ex") 5 | Code.require_file("test/support/user.ex") 6 | 7 | test "missing factory" do 8 | error_message = """ 9 | Could not find factory by `:foo`. 10 | Define it in your configuration: 11 | 12 | config :ecto_factory, factories: [ 13 | foo: Myapp.EctoModule 14 | ] 15 | """ 16 | 17 | assert_raise(EctoFactory.MissingFactory, error_message, fn -> 18 | EctoFactory.schema(:foo) 19 | end) 20 | end 21 | 22 | test "build by directly passing in a schema" do 23 | user = EctoFactory.schema(User) 24 | assert user 25 | assert Enum.member?([true, false], user.admin) 26 | assert is_map(user.avatar_url) 27 | end 28 | 29 | test "build with attributes" do 30 | user = EctoFactory.schema(User, username: "foo") 31 | assert user.username == "foo" 32 | end 33 | 34 | test "build by using defined factory and passed in attributes" do 35 | user = EctoFactory.schema(:user_with_default_username, age: 99) 36 | assert user.username == "mrmicahcooper" 37 | assert user.age == 99 38 | end 39 | 40 | test "build attrs by using defined factory and passed in attributes" do 41 | user = EctoFactory.attrs(:user_with_default_username, age: 99) 42 | assert Map.get(user, "username") == "mrmicahcooper" 43 | assert Map.get(user, "age") == 99 44 | end 45 | 46 | test "build attrs by using defined factory and a map of attributes" do 47 | user = EctoFactory.attrs(:user_with_default_username, %{age: 99}) 48 | assert Map.get(user, "username") == "mrmicahcooper" 49 | assert Map.get(user, "age") == 99 50 | end 51 | 52 | test "build a map of attrbitutes where the keys are atoms" do 53 | user = EctoFactory.build(:user_with_default_username, %{age: 99}) 54 | assert Map.get(user, :username) == "mrmicahcooper" 55 | assert Map.get(user, :age) == 99 56 | end 57 | 58 | test "using atoms for attributes for randomly generated things" do 59 | user = EctoFactory.schema(User, username: :email) 60 | assert String.match?(user.username, ~r/@/) 61 | end 62 | 63 | test "using tuples for attributes to randomly generate things" do 64 | user = EctoFactory.schema(User, username: {:array, :email}) 65 | assert user.username |> List.first() |> String.match?(~r/@/) 66 | end 67 | 68 | test "gen a url" do 69 | user = EctoFactory.schema(User, username: :url) 70 | assert Regex.match?(~r/^https.+\w+\..+\w+$/, user.username) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/support/blog_post.ex: -------------------------------------------------------------------------------- 1 | defmodule BlogPost do 2 | use Ecto.Schema 3 | 4 | schema "blog_posts" do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/support/ecto_uri.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoURI do 2 | use Ecto.Type 3 | def type, do: :map 4 | 5 | # Provide custom casting rules. 6 | # Cast strings into the URI struct to be used at runtime 7 | def cast(uri) when is_binary(uri) do 8 | {:ok, URI.parse(uri)} 9 | end 10 | 11 | # Accept casting of URI structs as well 12 | def cast(%URI{} = uri), do: {:ok, uri} 13 | 14 | # Everything else is a failure though 15 | def cast(_), do: :error 16 | 17 | # When loading data from the database, as long as it's a map, 18 | # we just put the data back into an URI struct to be stored in 19 | # the loaded schema struct. 20 | def load(data) when is_map(data) do 21 | data = 22 | for {key, val} <- data do 23 | {String.to_existing_atom(key), val} 24 | end 25 | {:ok, struct!(URI, data)} 26 | end 27 | 28 | # When dumping data to the database, we *expect* an URI struct 29 | # but any value could be inserted into the schema struct at runtime, 30 | # so we need to guard against them. 31 | def dump(%URI{} = uri), do: {:ok, Map.from_struct(uri)} 32 | def dump(_), do: :error 33 | end 34 | -------------------------------------------------------------------------------- /test/support/repo.ex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrmicahcooper/ecto_factory/9e2b7b2f856aa9c8387feb959bd9ce5098148c82/test/support/repo.ex -------------------------------------------------------------------------------- /test/support/role.ex: -------------------------------------------------------------------------------- 1 | defmodule Role do 2 | use Ecto.Schema 3 | 4 | schema "roles" do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/support/user.ex: -------------------------------------------------------------------------------- 1 | defmodule User do 2 | use Ecto.Schema 3 | alias EctoURI 4 | 5 | schema "users" do 6 | field(:username) 7 | field(:age, :integer) 8 | field(:date_of_birth, :date) 9 | field(:addresses, {:array, :string}) 10 | field(:profile, :map) 11 | field(:admin, :boolean) 12 | field(:avatar_url, EctoURI) 13 | has_many(:blog_posts, BlogPost) 14 | belongs_to(:role, Role) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------