Error messages (expected response with status 422, got: 201) when running tests

I’m running into an error when running the User Controller test in my Phoenix application. The test is failing with the following error:

1) test create user renders errors when data is invalid (DbserviceWeb.UserControllerTest)
     test/dbservice_web/controllers/user_controller_test.exs:106
     ** (RuntimeError) expected response with status 422, got: 201, with body:
     "{\"address\":null,\"city\":null,\"country\":null,\"date_of_birth\":null,\"district\":null,\"email\":null,\"full_name\":null,\"gender\":null,\"id\":466,\"phone\":null,\"pincode\":null,\"role\":null,\"state\":null,\"whatsapp_phone\":null}"
     code: assert json_response(conn, 422)["errors"] != %{}
     stacktrace:
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:369: Phoenix.ConnTest.response/2
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:415: Phoenix.ConnTest.json_response/2
       test/dbservice_web/controllers/user_controller_test.exs:108: (test)

It seems that the test is expecting a response with status 422, but is receiving a response with status 201 instead.

Here’s the test code:

  @valid_fields [
    "address",
    "city",
    "country",
    "date_of_birth",
    "district",
    "email",
    "full_name",
    "gender",
    "id",
    "phone",
    "pincode",
    "role",
    "state",
    "whatsapp_phone"
  ]
describe "create user" do
    test "renders user when data is valid", %{conn: conn} do
      conn = post(conn, Routes.user_path(conn, :create), @create_attrs)
      %{"id" => id} = json_response(conn, 201)

      conn = get(conn, Routes.user_path(conn, :show, id))
      IO.inspect(conn)

      assert %{
               "id" => ^id,
               "address" => "some address",
               "city" => "some city",
               "country" => "some country",
               "district" => "some district",
               "email" => "some email",
               "full_name" => "some full name",
               "gender" => "some gender",
               "phone" => "9456591269",
               "pincode" => "some pincode",
               "role" => "some role",
               "state" => "some state",
               "whatsapp_phone" => "some whatsapp phone"
             } = json_response(conn, 200)
    end

  test "renders errors when data is invalid", %{conn: conn} do
      conn = post(conn, Routes.user_path(conn, :create), user: @invalid_attrs)
      assert json_response(conn, 422)["errors"] != %{}
    end

And here’s the controller code:

  swagger_path :create do
    post("/api/user")

    parameters do
      body(:body, Schema.ref(:User), "User to create", required: true)
    end

    response(201, "Created", Schema.ref(:User))
  end

  def create(conn, params) do
    with {:ok, %User{} = user} <- Users.create_user(params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.user_path(conn, :show, user))
      |> render("show.json", user: user)
    end
  end

It suggests that @invalid_attrs successfully passed all validations and constraints and the user record got inserted to the DB. Could you verify that?

2 Likes

@RudManusachi No, there are no entries created in the test database after running the test.

this part of test code successfully passed the test .

describe "create user" do
    test "renders user when data is valid", %{conn: conn} do
      conn = post(conn, Routes.user_path(conn, :create), @create_attrs)
      %{"id" => id} = json_response(conn, 201)

      conn = get(conn, Routes.user_path(conn, :show, id))
      IO.inspect(conn)

      assert %{
               "id" => ^id,
               "address" => "some address",
               "city" => "some city",
               "country" => "some country",
               "district" => "some district",
               "email" => "some email",
               "full_name" => "some full name",
               "gender" => "some gender",
               "phone" => "9456591269",
               "pincode" => "some pincode",
               "role" => "some role",
               "state" => "some state",
               "whatsapp_phone" => "some whatsapp phone"
             } = json_response(conn, 200)
    end

but this part of code is giving error

  test "renders errors when data is invalid", %{conn: conn} do
      conn = post(conn, Routes.user_path(conn, :create), user: @invalid_attrs)
      assert json_response(conn, 422)["errors"] != %{}

there are no entries created in the test database after running the test.

You are probably running tests with Ecto.Adapters.SQL.Sandbox that’s why after running test there is nothing in the DB. (which is the right thing to do).

That error is not just an “error”, it indicates that test didn’t pass. After conn = post(conn, .. instead of asserting on the response and status - try something like Repo.all(User) |> IO.inspect() and see what’s printed out

@RudManusachi

 test "renders errors when data is invalid", %{conn: conn} do
      conn = post(conn, Routes.user_path(conn, :create), user: @invalid_attrs)
      Repo.all(User) |> IO.inspect()
      assert json_response(conn, 422)["errors"] != %{}
    end

In Repo.all(User) |> IO.inspect() I get the user.

[
  %Dbservice.Users.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "user">,
    id: 72,
    address: nil,
    city: "Glover",
    district: nil,
    email: "ottis2090@example.com",
    full_name: "Mrs. Coty Kemmer PhD",
    gender: "female",
    phone: "910626426",
    pincode: "56549",
    role: "admin",
    state: "Missouri",
    whatsapp_phone: "961243825",
    date_of_birth: ~D[2014-05-28],
    country: "Sri Lanka",
    inserted_at: ~N[2023-04-07 07:18:44],
    updated_at: ~N[2023-04-07 07:18:44],
    sessions: #Ecto.Association.NotLoaded<association :sessions is not loaded>,
    teacher: #Ecto.Association.NotLoaded<association :teacher is not loaded>,
    student: #Ecto.Association.NotLoaded<association :student is not loaded>,
    group: #Ecto.Association.NotLoaded<association :group is not loaded>
  },
  %Dbservice.Users.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "user">,
    id: 73,
    address: nil,
    city: "Kuvalis",
    district: nil,
    email: "hilton2029@example.com",
    full_name: "Alejandra Hessel",
    gender: "male",
    phone: "920433426",
    pincode: "34912",
    role: "admin",
    state: "Indiana",
    whatsapp_phone: "277170249",
    date_of_birth: ~D[2012-05-21],
    country: "Bhutan",
    inserted_at: ~N[2023-04-07 07:18:44],
    updated_at: ~N[2023-04-07 07:18:44],
    sessions: #Ecto.Association.NotLoaded<association :sessions is not loaded>,
    teacher: #Ecto.Association.NotLoaded<association :teacher is not loaded>,
    student: #Ecto.Association.NotLoaded<association :student is not loaded>,
    group: #Ecto.Association.NotLoaded<association :group is not loaded>
  },
  %Dbservice.Users.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "user">,
    id: 74,
    address: nil,
    city: "Port Ronny",
    district: nil,
    email: "nils.hammes@example.net",
    full_name: "Afton Friesen",
    gender: "male",
    phone: "931697753",
    pincode: "67263",
    role: "admin",
    state: "Nevada",
    whatsapp_phone: "927542323",
    date_of_birth: ~D[2018-03-29],
    country: "Bangladesh",
    inserted_at: ~N[2023-04-07 07:18:44],
    updated_at: ~N[2023-04-07 07:18:44],
    sessions: #Ecto.Association.NotLoaded<association :sessions is not loaded>,
    teacher: #Ecto.Association.NotLoaded<association :teacher is not loaded>,
    student: #Ecto.Association.NotLoaded<association :student is not loaded>,
    group: #Ecto.Association.NotLoaded<association :group is not loaded>
  },..........
 %Dbservice.Users.User{__meta__: #Ecto.Schema.Metadata<:loaded, "user">, ...},
  %Dbservice.Users.User{...},
  ...
]

and when I do IO.inspect(conn)
I get this response

%Plug.Conn{
  adapter: {Plug.Adapters.Test.Conn, :...},
  assigns: %{
    layout: false,
    user: %Dbservice.Users.User{
      __meta__: #Ecto.Schema.Metadata<:loaded, "user">,
      id: 483,
      address: nil,
      city: nil,
      district: nil,
      email: nil,
      full_name: nil,
      gender: nil,
      phone: nil,
      pincode: nil,
      role: nil,
      state: nil,
      whatsapp_phone: nil,
      date_of_birth: nil,
      country: nil,
      inserted_at: ~N[2023-04-07 10:16:12],
      updated_at: ~N[2023-04-07 10:16:12],
      sessions: #Ecto.Association.NotLoaded<association :sessions is not loaded>,
      teacher: #Ecto.Association.NotLoaded<association :teacher is not loaded>,
      student: #Ecto.Association.NotLoaded<association :student is not loaded>,
      group: #Ecto.Association.NotLoaded<association :group is not loaded>
    }
  },
  body_params: %{
    "user" => %{
      "address" => nil,
      "city" => nil,
      "date_of_birth" => nil,
      "district" => nil,
      "email" => nil,
      "full_name" => nil,
      "gender" => nil,
      "phone" => nil,
      "pincode" => nil,
      "role" => nil,
      "state" => nil,
      "whatsapp_phone" => nil
    }
  },
  cookies: %{},
  halted: false,
  host: "www.example.com",
  method: "POST",
  owner: #PID<0.465.0>,
  params: %{
    "user" => %{
      "address" => nil,
      "city" => nil,
      "date_of_birth" => nil,
      "district" => nil,
      "email" => nil,
      "full_name" => nil,
      "gender" => nil,
      "phone" => nil,
      "pincode" => nil,
      "role" => nil,
      "state" => nil,
      "whatsapp_phone" => nil
    }
  },
  path_info: ["api", "user"],
  path_params: %{},
  port: 80,
  private: %{
    DbserviceWeb.Router => {[], %{PhoenixSwagger.Plug.SwaggerUI => []}},
    :before_send => [#Function<0.11807388/1 in Plug.Telemetry.call/2>],
    :phoenix_action => :create,
    :phoenix_controller => DbserviceWeb.UserController,
    :phoenix_endpoint => DbserviceWeb.Endpoint,
    :phoenix_format => "json",
    :phoenix_layout => {DbserviceWeb.LayoutView, :app},
    :phoenix_recycled => false,
    :phoenix_request_logger => {"request_logger", "request_logger"},
    :phoenix_router => DbserviceWeb.Router,
    :phoenix_template => "show.json",
    :phoenix_view => DbserviceWeb.UserView,
    :plug_session_fetch => #Function<1.84243074/1 in Plug.Session.fetch_session/1>,
    :plug_skip_csrf_protection => true
  },
  query_params: %{},
  query_string: "",
  remote_ip: {127, 0, 0, 1},
  req_cookies: %{},
  req_headers: [
    {"accept", "application/json"},
    {"content-type", "multipart/mixed; boundary=plug_conn_test"}
  ],
  request_path: "/api/user",
  resp_body: "{\"address\":null,\"city\":null,\"country\":null,\"date_of_birth\":null,\"district\":null,\"email\":null,\"full_name\":null,\"gender\":null,\"id\":483,\"phone\":null,\"pincode\":null,\"role\":null,\"state\":null,\"whatsapp_phone\":null}",
  resp_cookies: %{},
  resp_headers: [
    {"content-type", "application/json; charset=utf-8"},
    {"cache-control", "max-age=0, private, must-revalidate"},
    {"x-request-id", "F1OfmoeRi3NDpVUAAAEC"},
    {"location", "/api/user/483"}
  ],
  scheme: :http,
  script_name: [],
  secret_key_base: :...,
  state: :sent,
  status: 201
}

We don’t have context: why is the test expecting 422?

HTTP 201 means “entry created” which seems right to do in this particular case. So it might be a case of wrong test expectation, or somebody not documenting why exactly should 422 be expected.

@dimitarvp The purpose of this test is to verify that the “create user” action correctly handles and reports validation errors when given invalid input data. The test sends a POST request to the create path with @invalid_attrs , which presumably contains invalid user information. If the action fails due to validation errors, the server should respond with an HTTP status code 422 (Unprocessable Entity) and a JSON representation of the validation errors. you will find this in below link.
reference - Testing Controllers — Phoenix v1.7.2

Validation requests usually get rejected with HTTP 400 Bad Request.

As for drilling down on your problem, going to the changeset function of the User schema module is a good start, have you tried that?

@dimitarvp yes
here is my user.ex

defmodule Dbservice.Users.User do
  @moduledoc false

  use Ecto.Schema
  import Ecto.Changeset
  import Dbservice.Utils.Util

  alias Dbservice.Sessions.SessionOccurence
  alias Dbservice.Users.Teacher
  alias Dbservice.Users.Student
  alias Dbservice.Groups.Group

  schema "user" do
    field :address, :string
    field :city, :string
    field :district, :string
    field :email, :string
    field :full_name, :string
    field :gender, :string
    field :phone, :string
    field :pincode, :string
    field :role, :string
    field :state, :string
    field :whatsapp_phone, :string
    field :date_of_birth, :date
    field :country, :string

    timestamps()

    many_to_many :sessions, SessionOccurence, join_through: "user_session", on_replace: :delete
    has_one :teacher, Teacher
    has_one :student, Student
    many_to_many :group, Group, join_through: "group_user", on_replace: :delete
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [
      :full_name,
      :email,
      :phone,
      :gender,
      :address,
      :city,
      :district,
      :state,
      :pincode,
      :role,
      :whatsapp_phone,
      :date_of_birth,
      :country
    ])
    |> validate_format(:phone, ~r{\A\d*\z})
    |> validate_date_of_birth
  end

  def changeset_update_groups(user, groups) do
    user
    |> change()
    |> put_assoc(:group, groups)
  end

  defp validate_date_of_birth(changeset) do
    if get_field(changeset, :date_of_birth) != nil do
      invalidate_future_date(changeset, :date_of_birth)
    else
      changeset
    end
  end
end

Hey @dimitarvp can you help me on my test case error which I describe above ?

@RudManusachi already gave you the answer, what’s unclear exactly?

@dimitarvp any solution on this error which is occurring after running the test ?

 test create user renders errors when data is invalid (DbserviceWeb.UserControllerTest)
     test/dbservice_web/controllers/user_controller_test.exs:106
     ** (RuntimeError) expected response with status 422, got: 201, with body:
     "{\"address\":null,\"city\":null,\"country\":null,\"date_of_birth\":null,\"district\":null,\"email\":null,\"full_name\":null,\"gender\":null,\"id\":466,\"phone\":null,\"pincode\":null,\"role\":null,\"state\":null,\"whatsapp_phone\":null}"
     code: assert json_response(conn, 422)["errors"] != %{}
     stacktrace:
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:369: Phoenix.ConnTest.response/2
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:415: Phoenix.ConnTest.json_response/2
       test/dbservice_web/controllers/user_controller_test.exs:108: (test)

Do you know how changesets work?

The root problem here is that User.changeset doesn’t require any fields, so sending it nils won’t cause it to be invalid.

HOWEVER

There are several other layers missing as well:

  • if there were validation errors, the code in the controller’s create function will make it fall-through and return {:error, changeset}. Do you have a fallback controller set up?

  • phoenix_swagger can help with this, but only if it’s correctly configured and enabled:

    • the name of the parameter create expects is user not body, the parameters block should start user(:body, Schema.ref(...)
    • the validations need to be wired up, either through the PhoenixSwagger.Plug.Validate plug or by directly calling PhoenixSwagger.ConnValidator.validate! directly.

@al2o3cr Yes I have fallback_controller.ex

defmodule DbserviceWeb.FallbackController do
  @moduledoc """
  Translates controller action results into valid `Plug.Conn` responses.

  See `Phoenix.Controller.action_fallback/1` for more details.
  """
  use DbserviceWeb, :controller

  # This clause handles errors returned by Ecto's insert/update/delete.
  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> put_view(DbserviceWeb.ChangesetView)
    |> render("error.json", changeset: changeset)
  end

  # This clause is an example of how to handle resources that cannot be found.
  def call(conn, {:error, :not_found}) do
    conn
    |> put_status(:not_found)
    |> put_view(DbserviceWeb.ErrorView)
    |> render(:"404")
  end
end

and further used in user controller

  action_fallback DbserviceWeb.FallbackController

@RudManusachi @dimitarvp @al2o3cr Thanks, The error has been resolved :slightly_smiling_face:

For thec benefit of future readers – how exactly?

@dimitarvp I don’t know exactly but yeah I add the validation required |> validate_required([:date_of_birth]) in changeset on my user.ex , I have one question that if there is no validation field , then how I can remove my above error ?

  @moduledoc false

  use Ecto.Schema
  import Ecto.Changeset
  import Dbservice.Utils.Util

  alias Dbservice.Sessions.SessionOccurence
  alias Dbservice.Users.Teacher
  alias Dbservice.Users.Student
  alias Dbservice.Groups.Group

  schema "user" do
    field :address, :string
    field :city, :string
    field :district, :string
    field :email, :string
    field :full_name, :string
    field :gender, :string
    field :phone, :string
    field :pincode, :string
    field :role, :string
    field :state, :string
    field :whatsapp_phone, :string
    field :date_of_birth, :date
    field :country, :string

    timestamps()

    many_to_many :sessions, SessionOccurence, join_through: "user_session", on_replace: :delete
    has_one :teacher, Teacher
    has_one :student, Student
    many_to_many :group, Group, join_through: "group_user", on_replace: :delete
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [
      :full_name,
      :email,
      :phone,
      :gender,
      :address,
      :city,
      :district,
      :state,
      :pincode,
      :role,
      :whatsapp_phone,
      :date_of_birth,
      :country
    ])
    |> validate_format(:phone, ~r{\A\d*\z})
    |> validate_date_of_birth
    |> validate_required([:date_of_birth])
  end

  def changeset_update_groups(user, groups) do
    user
    |> change()
    |> put_assoc(:group, groups)
  end

  defp validate_date_of_birth(changeset) do
    if get_field(changeset, :date_of_birth) != nil do
      invalidate_future_date(changeset, :date_of_birth)
    else
      changeset
    end
  end
end

@al2o3cr Hey can you please help me Here if there is no validate required field present then how can I remove this error ?

The other validations (validate_format, validate_date_of_birth) can only fail if there is data in the corresponding fields. Based on the output you’ve shown, the @invalid_attrs map in the test is all nils.

Try using non-nil values that are wrong, like phone: "nope".

@al2o3cr Thanks, I tried phone: "nope" , other error like update one is removed but still In create one I get this error.

1) test create user renders errors when data is invalid (DbserviceWeb.UserControllerTest)
     test/dbservice_web/controllers/user_controller_test.exs:105
     ** (RuntimeError) expected response with status 422, got: 201, with body:
     "{\"address\":null,\"city\":null,\"country\":null,\"date_of_birth\":null,\"district\":null,\"email\":null,\"full_name\":null,\"gender\":null,\"id\":415,\"phone\":null,\"pincode\":null,\"role\":null,\"state\":null,\"whatsapp_phone\":null}"
     code: assert json_response(conn, 422)["errors"] != %{}
     stacktrace:
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:369: Phoenix.ConnTest.response/2
       (phoenix 1.6.10) lib/phoenix/test/conn_test.ex:415: Phoenix.ConnTest.json_response/2
       test/dbservice_web/controllers/user_controller_test.exs:107: (test)