Search code examples
elixirintegration-testingphoenix-frameworkearly-return

Early return not working in integration tests in elixir/phoenix


I am totally new to Elixir, but when using Node we do a lot of early returns, but I'm having some problems to make an early-return-like code work in my integration tests. This is my problem:

I have this method in my controller that receives a JSON as param, I need to validate if the JSON contains some properties (key and type), if it doesn't I want to send an error to the client without the need to execute the rest of the method. This works perfectly when I make a request using Insomnia or Postman, but it doesn't work in my integration tests. The code below the conditional executes even when the conditional is falsy.

This is my method:

def create(conn, body) do
    if !PayeeContext.check_for_required_fields(body), do: conn
      |> send_resp(400, "Please provide Key and Type parameters")

    %{"key" => key} = body
    %{"type" => type} = body

    validation_func = PayeeContext.validation_map(type)

    case validation_func.(key) do
      {:ok, _} ->
        conn |> send_resp(200, "Request created successfully")
      {:error, message} -> conn |> send_resp(400, message)
    end
  end

and this is my test

test "returns 400 if Key prop is missing", %{conn: conn} do
      params = %{
        key: "44187221816",
        account: %{
          account_type: "checking_account",
          account_number: "00028363-6",
          branch: "0001"
        },
        owner: %{
          name: "Eve Montalvão"
        }
      }

      response = conn |> post(Routes.payee_path(conn, :create, params))

      assert response.status == 400
    end

and my PayeeContext.check_for_required_fields

  def check_for_required_fields(fields) do
    Enum.all?(@required_fields, fn field -> Map.has_key?(fields, field) end)
  end

What am I doing wrong?


Solution

  • If-statements are rare in Elixir: pattern matching is more idiomatic. The forking of execution flow can be hard to follow, and you'll find that "early returns" can be a bit confusing at best, or an anti-pattern at worst.

    Consider refactoring your code to do the pattern match in the function arguments. Not only does this eliminate the need for your validation function (because the pattern match will only succeed if those keys are present), it allows you to define multiple clauses of the create/2 function. This effectively gives you 2 execution paths: one for cases when the key and type parameters are present, and another when they are not.

    def create(conn, %{"key" => key, "type" => type}) do
    
        validation_func = PayeeContext.validation_map(type)
    
        case validation_func.(key) do
          {:ok, _} ->
            conn |> send_resp(200, "Request created successfully")
          {:error, message} -> conn |> send_resp(400, message)
        end
    end
    
    def create(conn, _) do
      send_resp(conn, 400, "Please provide Key and Type parameters")
    end
    

    Remember: the more specific matches must go first -- execution will be dispatched to the first function where a match succeeds.