{"__v":1,"_id":"5777c9695b2b430e00b982b9","category":{"version":"5777c9635b2b430e00b982a5","project":"54348ec95b10711400c6c445","_id":"5777c9635b2b430e00b982a8","__v":0,"sync":{"url":"","isSync":false},"reference":false,"createdAt":"2015-06-18T22:04:48.705Z","from_sync":false,"order":2,"slug":"testing","title":"Testing"},"parentDoc":null,"project":"54348ec95b10711400c6c445","user":"5435b410495d5d0800f3a603","version":{"__v":1,"_id":"5777c9635b2b430e00b982a5","project":"54348ec95b10711400c6c445","createdAt":"2016-07-02T14:02:11.084Z","releaseDate":"2016-07-02T14:02:11.084Z","categories":["5777c9635b2b430e00b982a6","5777c9635b2b430e00b982a7","5777c9635b2b430e00b982a8","5777c9635b2b430e00b982a9","5777c9635b2b430e00b982aa"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"1.2.0","version":"1.2.0"},"updates":["55ba92f08f4ef01900c0ae40","55ba94994a342419005e8202","561bc886c89cc30d00821600","5651ada5748238210092d1b6","567a349d76cd370d003c11d2"],"next":{"pages":[],"description":""},"createdAt":"2015-06-18T22:06:07.800Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":1,"body":"In the [Ecto Models Guide](http://www.phoenixframework.org/docs/ecto-models) we generated an HTML resource for users. This gave us a number of modules for free, including a user model and a user model test case. In this guide, we'll use the model and test case to work through the changes we made in the Ecto Models Guide in a test-driven way.\n\nFor those of us who haven't worked through the Ecto Models Guide, it's easy to catch up. Please see the \"Generating an HTML Resource\" section below.\n\nBefore we do anything else, let's run `mix test` to make sure our test suite runs cleanly.\n\n```console\n$ mix test\n................\n\nFinished in 0.6 seconds (0.5s on load, 0.1s on tests)\n16 tests, 0 failures\n\nRandomized with seed 638414\n```\n\nGreat. We've got sixteen tests and they are all passing!\n\n## Test Driving a Changeset\n\nThe focus of this guide is going to be on `test/models/user_test.exs`. Let's take a quick look to get familiar with it.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  use HelloPhoenix.ModelCase\n\n  alias HelloPhoenix.User\n\n  :::at:::valid_attrs %{bio: \"some content\", email: \"some content\", name: \"some content\", number_of_pets: 42}\n  @invalid_attrs %{}\n\n  test \"changeset with valid attributes\" do\n    changeset = User.changeset(%User{}, @valid_attrs)\n    assert changeset.valid?\n  end\n\n  test \"changeset with invalid attributes\" do\n    changeset = User.changeset(%User{}, @invalid_attrs)\n    refute changeset.valid?\n  end\nend\n```\n\nIn the first line, we `use HelloPhoenix.ModelCase`, which is defined in `test/support/model_case.ex`. `HelloPhoenix.ModelCase` is responsible for importing and aliasing all the necessary modules for all of our model cases. `HelloPhoenix.ModelCase` will also run all of our model tests within a database transaction unless we've tagged an individual test case with `:async`.\n\n> Note: We should not tag any model case that interacts with a database as `:async`. This may cause  erratic test results and possibly even deadlocks.\n\n`HelloPhoenix.ModelCase` is also a place to define any helper functions we might need to test our models. We get an example function `errors_on/2` for free, and we'll see how that works shortly.\n\nWe alias our `HelloPhoenix.User` module so that we can refer to its structs as `%User{}` instead of `%HelloPhoenix.User{}`.\n\nWe also define module attributes for `@valid_attrs` and `@invalid_attrs` so they will be available to all our tests.\n\nThe generated test attributes we get from `HelloPhoenix.UserTest` are certainly usable as is, but let's change them to look just a bit more realistic. The only one that will really matter is `:email`, as that will need to have an `@` before we're done. The other changes are just cosmetic.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  use HelloPhoenix.ModelCase\n\n  alias HelloPhoenix.User\n\n  @valid_attrs %{bio: \"my life\", email: \"pat@example.com\", name: \"Pat Example\", number_of_pets: 4}\n  @invalid_attrs %{}\n\n  ...\nend\n```\n\nWe should change the `@valid_attrs` module attribute in `test/controllers/user_controller_test.exs` to match these as well for consistency.\n\n```elixir\ndefmodule HelloPhoenix.UserControllerTest do\n  use HelloPhoenix.ConnCase\n\n  alias HelloPhoenix.User\n  @valid_attrs %{bio: \"my life\", email: \"pat@example.com\", name: \"Pat Example\", number_of_pets: 4}\n  @invalid_attrs %{}\n\n  ...\nend\n```\n\nIf we run the tests again, all sixteen should still pass.\n\n#### Number of Pets\n\nWhile Phoenix generated our model with all of the fields required, the number of pets a user has is optional in our domain.\n\nLet's write a new test to verify that.\n\nTo test this, we can delete the `:number_of_pets` key and value from the `@valid_attrs` map and make a `User` changeset from those new attributes. Then we can assert that the changeset is still valid.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  ...\n\n  test \"number_of_pets is not required\" do\n    changeset = User.changeset(%User{}, Map.delete(@valid_attrs, :number_of_pets))\n    assert changeset.valid?\n  end\nend\n```\n\nNow, let's run the tests again.\n\n```console\n$ mix test\n.............\n\n  1) test number_of_pets is not required (HelloPhoenix.UserTest)\n     test/models/user_test.exs:19\n     Expected truthy, got false\n     code: changeset.valid?()\n     stacktrace:\n       test/models/user_test.exs:21\n\n...\n\nFinished in 0.4 seconds (0.2s on load, 0.1s on tests)\n17 tests, 1 failure\n\nRandomized with seed 780208\n```\n\nIt fails - which is exactly what it should do! We haven't written the code to make it pass yet. To  do that, we need to move the `number_of_pets` attribute from `@required_fields` to `@optional_fields` in `web/models/user.ex`.\n\n```elixir\ndefmodule HelloPhoenix.User do\n  use HelloPhoenix.Web, :model\n\n  schema \"users\" do\n    field :name, :string\n    field :email, :string\n    field :bio, :string\n    field :number_of_pets, :integer\n\n    timestamps\n  end\n\n  @required_fields ~w(name email bio)\n  @optional_fields ~w(number_of_pets)\n\n  ...\nend\n```\n\nNow our tests are all passing again.\n\n```console\n$ mix test\n.................\n\nFinished in 0.3 seconds (0.2s on load, 0.09s on tests)\n17 tests, 0 failures\n\nRandomized with seed 963040\n```\n\n#### The Bio Attribute\n\nIn the Ecto Models Guide, we learned that the user's `:bio` attribute has two business requirements. The first is that it must be at least two characters long. Let's write a test for that using the same pattern we've just used.\n\nFirst, we change the `:bio` attribute to have a value of a single character. Then we create a changeset with the new attributes and test its validity.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  ...\n\n  test \"bio must be at least two characters long\" do\n    attrs = %{@valid_attrs | bio: \"I\"}\n    changeset = User.changeset(%User{}, attrs)\n    refute changeset.valid?\n  end\nend\n```\n\nWhen we run the test, it fails, as we would expect.\n\n```console\n$ mix test\n.....\n\n  1) test bio must be at least two characters long (HelloPhoenix.UserTest)\n     test/models/user_test.exs:24\n     Expected false or nil, got true\n     code: changeset.valid?()\n     stacktrace:\n       test/models/user_test.exs:27\n\n............\n\nFinished in 0.3 seconds (0.2s on load, 0.09s on tests)\n18 tests, 1 failure\n\nRandomized with seed 327779\n```\n\nHmmm. Yes, this test behaved as we expected, but the error message doesn't seem to reflect our test. We're validating the length of the `:bio` attribute, and the message we get is \"Expected false or nil, got true\". There's no mention of our `:bio` attribute at all.\n\nWe can do better.\n\nLet's change our test to get a better message while still testing the same behavior. We can leave the code to set the new `:bio` value in place. In the `assert`, however, we'll use the `errors_on/2` function we get from `ModelCase` to generate a list of errors, and check that the `:bio` attribute error is in that list.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  ...\n\n  test \"bio must be at least two characters long\" do\n    attrs = %{@valid_attrs | bio: \"I\"}\n    assert {:bio, \"should be at least 2 character(s)\"} in errors_on(%User{}, attrs)\n  end\nend\n```\n\n> Note: `ModelCase.errors_on/2` returns a keyword list, and an individual element of a keyword list is a tuple.\n\nWhen we run the tests again, we get a different message entirely.\n\n```console\n$ mix test\n...............\n\n  1) test bio must be at least two characters long (HelloPhoenix.UserTest)\n     test/models/user_test.exs:24\n     Assertion with in failed\n     code: {:bio, \"should be at least 2 character(s)\"} in errors_on(%User{}, attrs)\n     lhs:  {:bio,\n            \"should be at least 2 character(s)\"}\n     rhs:  []\n\n..\n\nFinished in 0.4 seconds (0.2s on load, 0.1s on tests)\n18 tests, 1 failure\n\nRandomized with seed 435902\n```\n\nThis shows us the assertion we are testing - that our error is in the list of errors from the model's changeset.\n\n```console\ncode: {:bio, \"should be at least 2 character(s)\"} in errors_on(%User{}, attrs)\n```\n\nWe see that the left hand side of the expression evaluates to our error.\n\n```console\nlhs:  {:bio, \"should be at least 2 character(s)\"}\n```\n\nAnd we see that the right hand side of the expression evaluates to an empty list.\n\n```console\nrhs:  []\n```\n\nThat list is empty because we don't yet validate the minimum length of the `:bio` attribute.\n\nOur test has pointed the way. Now let's make it pass by adding that validation.\n\n```elixir\ndefmodule HelloPhoenix.User do\n  ...\n\n  def changeset(model, params \\\\ :empty) do\n    model\n    |> cast(params, @required_fields, @optional_fields)\n    |> validate_length(:bio, min: 2)\n  end\nend\n```\n\nWhen we run the tests again, they all pass.\n\n```console\n$ mix test\n..................\n\nFinished in 0.3 seconds (0.2s on load, 0.09s on tests)\n18 tests, 0 failures\n\nRandomized with seed 305958\n```\n\nThe other business requirement for the `:bio` field is that it be a maximum of one hundred and forty characters. Let's write a test for that using the `errors_on/2` function again.\n\nBefore we actually write the test, how are we going to handle a string that long without making a mess? A new function in `HelloPhoenix.ModelCase` is perfect for this. We'll create a `long_string/1` function which will send us back a string of \"a\"'s as long as we tell it to be.\n\n```elixir\ndefmodule HelloPhoenix.ModelCase do\n  ...\n\n  def long_string(length) do\n    Enum.reduce (1..length), \"\", fn _, acc ->  acc <> \"a\" end\n  end\nend\n```\n\nWe can now use `long_string/1` when changing the value of the `:bio` key in our `attrs`.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  ...\n\n  test \"bio must be at most 140 characters long\" do\n    attrs = %{@valid_attrs | bio: long_string(141)}\n    assert {:bio, \"should be at most 140 character(s)\"} in errors_on(%User{}, attrs)\n  end\nend\n```\n\nWhen we run the test, it fails as we want it to.\n\n```console\n$ mix test\n....\n\n  1) test bio must be at most 140 characters long (HelloPhoenix.UserTest)\n     test/models/user_test.exs:29\n     Assertion with in failed\n     code: {:bio, {:bio, \"should be at most 140 character(s)\"} in errors_on(%User{}, attrs)\n     lhs:  {:bio,\n            \"should be at most 120 character(s)\"}\n\n..............\n\nFinished in 0.3 seconds (0.2s on load, 0.1s on tests)\n19 tests, 1 failure\n\nRandomized with seed 593838\n```\n\nTo make this test pass, we need to add a new validation for the maximum length of the `:bio` attribute.\n\n```elixir\ndefmodule HelloPhoenix.User do\n  ...\n\n  def changeset(model, params \\\\ :empty) do\n    model\n    |> cast(params, @required_fields, @optional_fields)\n    |> validate_length(:bio, min: 2)\n    |> validate_length(:bio, max: 140)\n  end\nend\n```\n\nWhen we run the tests, they all pass.\n\n```console\n$ mix test\n...................\n\nFinished in 0.4 seconds (0.3s on load, 0.1s on tests)\n19 tests, 0 failures\n\nRandomized with seed 468975\n```\n\n#### The Email Attribute\n\nWe have one last attribute to validate. Currently, `:email` is just a string like any other. We'd like to make sure that it at least matches an \"@\". This is no substitute for an email confirmation, but it will weed out some invalid addresses before we even try.\n\nThis process will feel familiar by now. First, we change the value of the `:email` attribute to omit the \"@\". Then we write an assertion which uses `errors_on/2` to check for the correct validation error on the `:email` attribute.\n\n```elixir\ndefmodule HelloPhoenix.UserTest do\n  ...\n\n  test \"email must contain at least an @\" do\n    attrs = %{@valid_attrs | email: \"fooexample.com\"}\n    assert {:email, \"has invalid format\"} in errors_on(%User{}, attrs)\n  end\nend\n```\n\nWhen we run the tests, it fails. We see that we're getting an empty list of errors back from `errors_on/2`.\n\n```console\n$ mix test\n................\n\n  1) test email must contain at least an @ (HelloPhoenix.UserTest)\n     test/models/user_test.exs:34\n     Assertion with in failed\n     code: {:email, \"has invalid format\"} in errors_on(%User{}, attrs)\n     lhs:  {:email, \"has invalid format\"}\n     rhs:  []\n     stacktrace:\n       test/models/user_test.exs:36\n\n...\n\nFinished in 0.4 seconds (0.2s on load, 0.1s on tests)\n20 tests, 1 failure\n\nRandomized with seed 962127\n```\n\nThen we add the new validation to generate the error our test is looking for.\n\n```elixir\ndefmodule HelloPhoenix.User do\n  ...\n\n  def changeset(model, params \\\\ :empty) do\n    model\n    |> cast(params, @required_fields, @optional_fields)\n    |> validate_length(:bio, min: 2)\n    |> validate_length(:bio, max: 140)\n    |> validate_format(:email, ~r/@/)\n  end\nend\n```\n\nNow all the tests are passing again.\n\n```console\n$ mix test\n....................\n\nFinished in 0.3 seconds (0.2s on load, 0.09s on tests)\n20 tests, 0 failures\n\nRandomized with seed 330955\n```\n\n### Generating an HTML Resource\n\nFor this section, we're going to assume that we all have a PostgreSQL database installed on our system, and that we generated a default application - one in which Ecto and Postgrex are installed and configured automatically.\n\nIf this is not the case, please see the section on adding Ecto and Postgrex of the [Ecto Models Guide](http://www.phoenixframework.org/docs/ecto-models#section-adding-ecto-and-postgrex-as-dependencies) and join us when that's done.\n\nOk, once we're all configured properly, we need to run the `phoenix.gen.html` task with the list of attributes we have here.\n\n```console\n$ mix phoenix.gen.html User users name:string email:string bio:string number_of_pets:integer\n* creating priv/repo/migrations/20150409213440_create_user.exs\n* creating web/models/user.ex\n* creating test/models/user_test.exs\n* creating web/controllers/user_controller.ex\n* creating web/templates/user/edit.html.eex\n* creating web/templates/user/form.html.eex\n* creating web/templates/user/index.html.eex\n* creating web/templates/user/new.html.eex\n* creating web/templates/user/show.html.eex\n* creating web/views/user_view.ex\n* creating test/controllers/user_controller_test.exs\n\nAdd the resource to your browser scope in web/router.ex:\n\n    resources \"/users\", UserController\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nThen we need to follow the instructions the task gives us and insert the `resources \"/users\", UserController` line in the router `web/router.ex`.\n\n```elixir\ndefmodule HelloPhoenix.Router do\n  ...\n\n  scope \"/\", HelloPhoenix do\n    pipe_through :browser # Use the default browser stack\n\n    get \"/\", PageController, :index\n    resources \"/users\", UserController\n  end\n\n  # Other scopes may use custom stacks.\n  # scope \"/api\", HelloPhoenix do\n  #   pipe_through :api\n  # end\nend\n```\n\nWith that done, we can create our database with `ecto.create`.\n\n```console\n$ mix ecto.create\nThe database for HelloPhoenix.Repo has been created.\n```\n\nThen we can migrate our database to create our `users` table with `ecto.migrate`.\n\n```console\n$ mix ecto.migrate\n\n[info]  == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward\n\n[info]  create table users\n\n[info]  == Migrated in 0.0s\n```\n\nWith that, we are ready to continue with the testing guide.","excerpt":"","slug":"models","type":"basic","title":"Models"}
In the [Ecto Models Guide](http://www.phoenixframework.org/docs/ecto-models) we generated an HTML resource for users. This gave us a number of modules for free, including a user model and a user model test case. In this guide, we'll use the model and test case to work through the changes we made in the Ecto Models Guide in a test-driven way. For those of us who haven't worked through the Ecto Models Guide, it's easy to catch up. Please see the "Generating an HTML Resource" section below. Before we do anything else, let's run `mix test` to make sure our test suite runs cleanly. ```console $ mix test ................ Finished in 0.6 seconds (0.5s on load, 0.1s on tests) 16 tests, 0 failures Randomized with seed 638414 ``` Great. We've got sixteen tests and they are all passing! ## Test Driving a Changeset The focus of this guide is going to be on `test/models/user_test.exs`. Let's take a quick look to get familiar with it. ```elixir defmodule HelloPhoenix.UserTest do use HelloPhoenix.ModelCase alias HelloPhoenix.User @valid_attrs %{bio: "some content", email: "some content", name: "some content", number_of_pets: 42} @invalid_attrs %{} test "changeset with valid attributes" do changeset = User.changeset(%User{}, @valid_attrs) assert changeset.valid? end test "changeset with invalid attributes" do changeset = User.changeset(%User{}, @invalid_attrs) refute changeset.valid? end end ``` In the first line, we `use HelloPhoenix.ModelCase`, which is defined in `test/support/model_case.ex`. `HelloPhoenix.ModelCase` is responsible for importing and aliasing all the necessary modules for all of our model cases. `HelloPhoenix.ModelCase` will also run all of our model tests within a database transaction unless we've tagged an individual test case with `:async`. > Note: We should not tag any model case that interacts with a database as `:async`. This may cause erratic test results and possibly even deadlocks. `HelloPhoenix.ModelCase` is also a place to define any helper functions we might need to test our models. We get an example function `errors_on/2` for free, and we'll see how that works shortly. We alias our `HelloPhoenix.User` module so that we can refer to its structs as `%User{}` instead of `%HelloPhoenix.User{}`. We also define module attributes for `@valid_attrs` and `@invalid_attrs` so they will be available to all our tests. The generated test attributes we get from `HelloPhoenix.UserTest` are certainly usable as is, but let's change them to look just a bit more realistic. The only one that will really matter is `:email`, as that will need to have an `@` before we're done. The other changes are just cosmetic. ```elixir defmodule HelloPhoenix.UserTest do use HelloPhoenix.ModelCase alias HelloPhoenix.User @valid_attrs %{bio: "my life", email: "pat@example.com", name: "Pat Example", number_of_pets: 4} @invalid_attrs %{} ... end ``` We should change the `@valid_attrs` module attribute in `test/controllers/user_controller_test.exs` to match these as well for consistency. ```elixir defmodule HelloPhoenix.UserControllerTest do use HelloPhoenix.ConnCase alias HelloPhoenix.User @valid_attrs %{bio: "my life", email: "pat@example.com", name: "Pat Example", number_of_pets: 4} @invalid_attrs %{} ... end ``` If we run the tests again, all sixteen should still pass. #### Number of Pets While Phoenix generated our model with all of the fields required, the number of pets a user has is optional in our domain. Let's write a new test to verify that. To test this, we can delete the `:number_of_pets` key and value from the `@valid_attrs` map and make a `User` changeset from those new attributes. Then we can assert that the changeset is still valid. ```elixir defmodule HelloPhoenix.UserTest do ... test "number_of_pets is not required" do changeset = User.changeset(%User{}, Map.delete(@valid_attrs, :number_of_pets)) assert changeset.valid? end end ``` Now, let's run the tests again. ```console $ mix test ............. 1) test number_of_pets is not required (HelloPhoenix.UserTest) test/models/user_test.exs:19 Expected truthy, got false code: changeset.valid?() stacktrace: test/models/user_test.exs:21 ... Finished in 0.4 seconds (0.2s on load, 0.1s on tests) 17 tests, 1 failure Randomized with seed 780208 ``` It fails - which is exactly what it should do! We haven't written the code to make it pass yet. To do that, we need to move the `number_of_pets` attribute from `@required_fields` to `@optional_fields` in `web/models/user.ex`. ```elixir defmodule HelloPhoenix.User do use HelloPhoenix.Web, :model schema "users" do field :name, :string field :email, :string field :bio, :string field :number_of_pets, :integer timestamps end @required_fields ~w(name email bio) @optional_fields ~w(number_of_pets) ... end ``` Now our tests are all passing again. ```console $ mix test ................. Finished in 0.3 seconds (0.2s on load, 0.09s on tests) 17 tests, 0 failures Randomized with seed 963040 ``` #### The Bio Attribute In the Ecto Models Guide, we learned that the user's `:bio` attribute has two business requirements. The first is that it must be at least two characters long. Let's write a test for that using the same pattern we've just used. First, we change the `:bio` attribute to have a value of a single character. Then we create a changeset with the new attributes and test its validity. ```elixir defmodule HelloPhoenix.UserTest do ... test "bio must be at least two characters long" do attrs = %{@valid_attrs | bio: "I"} changeset = User.changeset(%User{}, attrs) refute changeset.valid? end end ``` When we run the test, it fails, as we would expect. ```console $ mix test ..... 1) test bio must be at least two characters long (HelloPhoenix.UserTest) test/models/user_test.exs:24 Expected false or nil, got true code: changeset.valid?() stacktrace: test/models/user_test.exs:27 ............ Finished in 0.3 seconds (0.2s on load, 0.09s on tests) 18 tests, 1 failure Randomized with seed 327779 ``` Hmmm. Yes, this test behaved as we expected, but the error message doesn't seem to reflect our test. We're validating the length of the `:bio` attribute, and the message we get is "Expected false or nil, got true". There's no mention of our `:bio` attribute at all. We can do better. Let's change our test to get a better message while still testing the same behavior. We can leave the code to set the new `:bio` value in place. In the `assert`, however, we'll use the `errors_on/2` function we get from `ModelCase` to generate a list of errors, and check that the `:bio` attribute error is in that list. ```elixir defmodule HelloPhoenix.UserTest do ... test "bio must be at least two characters long" do attrs = %{@valid_attrs | bio: "I"} assert {:bio, "should be at least 2 character(s)"} in errors_on(%User{}, attrs) end end ``` > Note: `ModelCase.errors_on/2` returns a keyword list, and an individual element of a keyword list is a tuple. When we run the tests again, we get a different message entirely. ```console $ mix test ............... 1) test bio must be at least two characters long (HelloPhoenix.UserTest) test/models/user_test.exs:24 Assertion with in failed code: {:bio, "should be at least 2 character(s)"} in errors_on(%User{}, attrs) lhs: {:bio, "should be at least 2 character(s)"} rhs: [] .. Finished in 0.4 seconds (0.2s on load, 0.1s on tests) 18 tests, 1 failure Randomized with seed 435902 ``` This shows us the assertion we are testing - that our error is in the list of errors from the model's changeset. ```console code: {:bio, "should be at least 2 character(s)"} in errors_on(%User{}, attrs) ``` We see that the left hand side of the expression evaluates to our error. ```console lhs: {:bio, "should be at least 2 character(s)"} ``` And we see that the right hand side of the expression evaluates to an empty list. ```console rhs: [] ``` That list is empty because we don't yet validate the minimum length of the `:bio` attribute. Our test has pointed the way. Now let's make it pass by adding that validation. ```elixir defmodule HelloPhoenix.User do ... def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) |> validate_length(:bio, min: 2) end end ``` When we run the tests again, they all pass. ```console $ mix test .................. Finished in 0.3 seconds (0.2s on load, 0.09s on tests) 18 tests, 0 failures Randomized with seed 305958 ``` The other business requirement for the `:bio` field is that it be a maximum of one hundred and forty characters. Let's write a test for that using the `errors_on/2` function again. Before we actually write the test, how are we going to handle a string that long without making a mess? A new function in `HelloPhoenix.ModelCase` is perfect for this. We'll create a `long_string/1` function which will send us back a string of "a"'s as long as we tell it to be. ```elixir defmodule HelloPhoenix.ModelCase do ... def long_string(length) do Enum.reduce (1..length), "", fn _, acc -> acc <> "a" end end end ``` We can now use `long_string/1` when changing the value of the `:bio` key in our `attrs`. ```elixir defmodule HelloPhoenix.UserTest do ... test "bio must be at most 140 characters long" do attrs = %{@valid_attrs | bio: long_string(141)} assert {:bio, "should be at most 140 character(s)"} in errors_on(%User{}, attrs) end end ``` When we run the test, it fails as we want it to. ```console $ mix test .... 1) test bio must be at most 140 characters long (HelloPhoenix.UserTest) test/models/user_test.exs:29 Assertion with in failed code: {:bio, {:bio, "should be at most 140 character(s)"} in errors_on(%User{}, attrs) lhs: {:bio, "should be at most 120 character(s)"} .............. Finished in 0.3 seconds (0.2s on load, 0.1s on tests) 19 tests, 1 failure Randomized with seed 593838 ``` To make this test pass, we need to add a new validation for the maximum length of the `:bio` attribute. ```elixir defmodule HelloPhoenix.User do ... def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) |> validate_length(:bio, min: 2) |> validate_length(:bio, max: 140) end end ``` When we run the tests, they all pass. ```console $ mix test ................... Finished in 0.4 seconds (0.3s on load, 0.1s on tests) 19 tests, 0 failures Randomized with seed 468975 ``` #### The Email Attribute We have one last attribute to validate. Currently, `:email` is just a string like any other. We'd like to make sure that it at least matches an "@". This is no substitute for an email confirmation, but it will weed out some invalid addresses before we even try. This process will feel familiar by now. First, we change the value of the `:email` attribute to omit the "@". Then we write an assertion which uses `errors_on/2` to check for the correct validation error on the `:email` attribute. ```elixir defmodule HelloPhoenix.UserTest do ... test "email must contain at least an @" do attrs = %{@valid_attrs | email: "fooexample.com"} assert {:email, "has invalid format"} in errors_on(%User{}, attrs) end end ``` When we run the tests, it fails. We see that we're getting an empty list of errors back from `errors_on/2`. ```console $ mix test ................ 1) test email must contain at least an @ (HelloPhoenix.UserTest) test/models/user_test.exs:34 Assertion with in failed code: {:email, "has invalid format"} in errors_on(%User{}, attrs) lhs: {:email, "has invalid format"} rhs: [] stacktrace: test/models/user_test.exs:36 ... Finished in 0.4 seconds (0.2s on load, 0.1s on tests) 20 tests, 1 failure Randomized with seed 962127 ``` Then we add the new validation to generate the error our test is looking for. ```elixir defmodule HelloPhoenix.User do ... def changeset(model, params \\ :empty) do model |> cast(params, @required_fields, @optional_fields) |> validate_length(:bio, min: 2) |> validate_length(:bio, max: 140) |> validate_format(:email, ~r/@/) end end ``` Now all the tests are passing again. ```console $ mix test .................... Finished in 0.3 seconds (0.2s on load, 0.09s on tests) 20 tests, 0 failures Randomized with seed 330955 ``` ### Generating an HTML Resource For this section, we're going to assume that we all have a PostgreSQL database installed on our system, and that we generated a default application - one in which Ecto and Postgrex are installed and configured automatically. If this is not the case, please see the section on adding Ecto and Postgrex of the [Ecto Models Guide](http://www.phoenixframework.org/docs/ecto-models#section-adding-ecto-and-postgrex-as-dependencies) and join us when that's done. Ok, once we're all configured properly, we need to run the `phoenix.gen.html` task with the list of attributes we have here. ```console $ mix phoenix.gen.html User users name:string email:string bio:string number_of_pets:integer * creating priv/repo/migrations/20150409213440_create_user.exs * creating web/models/user.ex * creating test/models/user_test.exs * creating web/controllers/user_controller.ex * creating web/templates/user/edit.html.eex * creating web/templates/user/form.html.eex * creating web/templates/user/index.html.eex * creating web/templates/user/new.html.eex * creating web/templates/user/show.html.eex * creating web/views/user_view.ex * creating test/controllers/user_controller_test.exs Add the resource to your browser scope in web/router.ex: resources "/users", UserController Remember to update your repository by running migrations: $ mix ecto.migrate ``` Then we need to follow the instructions the task gives us and insert the `resources "/users", UserController` line in the router `web/router.ex`. ```elixir defmodule HelloPhoenix.Router do ... scope "/", HelloPhoenix do pipe_through :browser # Use the default browser stack get "/", PageController, :index resources "/users", UserController end # Other scopes may use custom stacks. # scope "/api", HelloPhoenix do # pipe_through :api # end end ``` With that done, we can create our database with `ecto.create`. ```console $ mix ecto.create The database for HelloPhoenix.Repo has been created. ``` Then we can migrate our database to create our `users` table with `ecto.migrate`. ```console $ mix ecto.migrate [info] == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward [info] create table users [info] == Migrated in 0.0s ``` With that, we are ready to continue with the testing guide.