I'm writing a controller function where it will check for a condition ( keyword to be valid ) before either to render a json object or error object if failed.
router.ex
scope "/api", DongNghiaWeb do
pipe_through :api
scope "/tim_kiem" do
get "/tu/:word" , APIWordController, :search_word
[...]
word_controller.ex
def search_word(conn, %{"word" => word}) do
conn
|> check_invalid_keyword(word)
|> render("search.json", words: word |> String.trim |> Words.suggest)
end
defp check_invalid_keyword(conn, keyword) do
unless Words.keyword_valid?(String.trim(keyword)) do
conn
|> put_status(400)
|> json(%{
error: "Invalid keyword"
})
end
conn
end
word_controller_test.ex
test "response error when word is not valid", %{conn: conn} do
response = get(conn,api_word_path(conn, :search_word, "a3d"))
|> json_response(400)
assert response["error"] == "Invalid keyword"
end
When running mix test
, the results will be like so :
** (RuntimeError) expected response with status 400, got: 200, with body: {"data":[]}
But when I try testing with a REST client ( Insomnia, for example ), the json will return to be { error : "Invalid keyword" }
just fine.
Your code is writing the response to the connection twice when Words.keyword_valid?(String.trim(keyword))
is falsy.
The first write happens when you call |> json(...)
and the second one when you call render
. Your code does not prevent render
being called when json
has already been called. The browser connection ends after the first write, so you see the right output with Insomnia but the testing setup uses the last response written to the connection.
Fixing this requires a bit of restructuring of your code. Here's how I would do it:
def search_word(conn, %{"word" => word}) do
if Words.keyword_valid?(String.trim(word)) do
conn
|> render("search.json", words: word |> String.trim() |> Words.suggest())
else
conn
|> put_status(400)
|> json(%{
error: "Invalid keyword"
})
end
end
Edit: here's one way to do what you requested in the comment below:
def search_word(conn, %{"word" => word}) do
with {:ok, conn} <- check_invalid_keyword(conn, word) do
conn
|> render("search.json", words: word |> String.trim() |> Words.suggest())
end
end
def check_invalid_keyword(conn, keyword) do
if Words.keyword_valid?(String.trim(keyword)) do
{:ok, conn}
else
conn
|> put_status(400)
|> json(%{
error: "Invalid keyword"
})
end
end
When the keyword is invalid, the return value fails to match the with
clause and is returned as is. If it was valid, the do
block gets executed.