查看源代码 Phoenix.ConnTest (Phoenix v1.7.14)
用于测试 Phoenix 端点和连接相关帮助器的便利功能。
您可能希望使用此模块或将其作为您的 ExUnit.CaseTemplate
的一部分。使用后,此模块会自动导入此处定义的所有函数以及 Plug.Conn
中的函数。
端点测试
Phoenix.ConnTest
通常针对端点工作。这是测试路由器分派到任何内容的首选方法。
@endpoint MyAppWeb.Endpoint
test "says welcome on the home page" do
conn = get(build_conn(), "/")
assert conn.resp_body =~ "Welcome!"
end
test "logs in" do
conn = post(build_conn(), "/login", [username: "john", password: "doe"])
assert conn.resp_body =~ "Logged in!"
end
@endpoint
模块属性包含要测试的端点,最常见的是您的应用程序端点本身。如果您使用 Phoenix 生成的 MyApp.ConnCase,它会自动为您设置。
与路由器和控制器一样,连接是测试中的主要抽象。 build_conn()
返回一个新连接,此模块中的函数可用于在分派到端点之前操作连接。
例如,可以为 json 请求设置 accepts 标头,如下所示
build_conn()
|> put_req_header("accept", "application/json")
|> get("/")
您也可以创建自己的帮助器,例如 json_conn()
,它使用 build_conn/0
和 put_req_header/3
,这样您就可以避免在整个测试中重复连接设置。
控制器测试
此模块中的函数也可用于控制器测试。虽然端点测试优先于控制器测试,特别是由于 Phoenix 中的控制器在您的域和视图之间起着集成作用,但在某些情况下,对控制器进行单元测试可能会有所帮助。
对于这种情况,您需要将 @endpoint
属性设置为您的控制器,并传递一个表示要分派的 action 的原子值。
@endpoint MyAppWeb.HomeController
test "says welcome on the home page" do
conn = get(build_conn(), :index)
assert conn.resp_body =~ "Welcome!"
end
请记住,一旦 @endpoint
变量被设置,所有后续测试都会受到影响。
视图测试
在其他情况下,您可能正在测试一个视图或其他需要连接才能进行处理的层。对于这种情况,可以使用 build_conn/3
帮助器创建连接。
MyApp.UserView.render("hello.html", conn: build_conn(:get, "/"))
虽然 build_conn/0
返回一个没有任何请求信息的连接,但 build_conn/3
返回一个包含给定请求信息的连接。
循环利用
浏览器使用 cookie 实现存储。当在响应中设置 cookie 时,浏览器会将其存储并在下一个请求中发送。
为了模拟这种行为,此模块提供了循环利用的概念。 recycle/1
函数接收一个连接并返回一个新连接,类似于 build_conn/0
返回的连接,其中所有来自上一个连接的响应 cookie 都定义为请求标头。这在测试需要 cookie 或会话才能正常工作的多个路由时非常有用。
请记住,Phoenix 会在分派之间自动循环利用连接。这通常在大多数情况下都能奏效,但如果您在下次分派之前修改了连接,则可能会丢弃信息。
# No recycling as the connection is fresh
conn = get(build_conn(), "/")
# The connection is recycled, creating a new one behind the scenes
conn = post(conn, "/login")
# We can also recycle manually in case we want custom headers
conn =
conn
|> recycle()
|> put_req_header("x-special", "nice")
# No recycling as we did it explicitly
conn = delete(conn, "/logout")
循环利用还会循环利用 “accept” 和 “authorization” 标头,以及对等数据信息。
总结
函数
断言错误已包装并使用给定状态发送。
创建一个要在即将到来的请求中使用的连接。
创建一个要在即将到来的请求中使用的连接,其中预设了方法、路径和主体。
调用端点和路由器管道。
调用当前路由的端点和路由器管道。
调用端点和给定的路由器管道。
清除闪存存储。
删除请求 cookie。
如果连接尚未循环利用,则确保循环利用连接。
获取闪存存储。
获取整个闪存存储。
从闪存存储中获取给定的键。
断言给定的状态代码,我们有一个 html 响应,并返回设置或发送的响应正文(如果有)。
初始化专门用于测试的会话。
断言给定的状态代码,我们有一个 json 响应,并返回设置或发送的解码 JSON 响应(如果有)。
返回 %Plug.Conn{}
的路由器对 URL 的匹配参数。
将给定的值放在闪存存储中的 key 下。
放置请求 cookie。
返回连接重定向到的 URL 的匹配参数。
返回给定重定向响应的 Location 标头。
断言给定的状态代码,并返回设置或发送的响应正文(如果有)。
返回内容类型(只要它与给定格式匹配)。
断言给定的状态代码,我们有一个文本响应,并返回设置或发送的响应正文(如果有)。
函数
断言错误已包装并使用给定状态发送。
用于测试您期望引发错误并使用 HTTP 状态包装响应的操作,内容通常由您的 MyAppWeb.ErrorHTML 视图呈现。
该函数接受一个状态,可以是整数 HTTP 状态或原子值,例如 500
或 :internal_server_error
。允许的原子值列表在 Plug.Conn.Status
中提供。如果引发了错误,则会返回一个包含包装响应的三元组,该三元组与响应的状态、标头和正文匹配。
{500, [{"content-type", "text/html"} | _], "Internal Server Error"}
示例
assert_error_sent :internal_server_error, fn ->
get(build_conn(), "/broken/route")
end
response = assert_error_sent 500, fn ->
get(build_conn(), "/broken/route")
end
assert {500, [_h | _t], "Internal Server Error"} = response
这也可用于测试导致错误的路由,该错误已通过 Plug.Status
协议转换为特定的响应,例如 Ecto.NoResultsError
assert_error_sent :not_found, fn ->
get(build_conn(), "/something-that-raises-no-results-error")
end
注意:对于不引发错误而是返回状态的路由,您应该直接测试响应。
conn = get(build_conn(), "/users/not-found")
assert response(conn, 404)
@spec build_conn() :: Plug.Conn.t()
创建一个要在即将到来的请求中使用的连接。
创建一个要在即将到来的请求中使用的连接,其中预设了方法、路径和主体。
这在需要特定连接来测试插件或特定函数时非常有用。
@spec bypass_through(Plug.Conn.t()) :: Plug.Conn.t()
调用端点和路由器管道。
用于对需要端点和/或路由器管道插件才能进行正确设置的插件进行单元测试。
注意以下示例中 bypass_through
后面的 get("/")
的用法。要执行插件管道,您必须对路由器发出请求。最常见的是,您可以简单地向根路径发送 GET 请求,但您也可以指定管道可能针对的不同方法或路径。
示例
例如,假设您要单独测试身份验证插件,但您需要调用端点插件和路由器管道来设置会话和闪存相关的依赖项。一种选择是调用使用适当管道的现有路由。您可以通过将连接和路由器名称传递给 bypass_through
来执行此操作。
conn =
conn
|> bypass_through(MyAppWeb.Router)
|> get("/some_url")
|> MyApp.RequireAuthentication.call([])
assert conn.halted
您还可以指定要运行的管道。
conn =
conn
|> bypass_through(MyAppWeb.Router, [:browser])
|> get("/")
|> MyApp.RequireAuthentication.call([])
assert conn.halted
或者,您也可以只调用端点插件。
conn =
conn
|> bypass_through()
|> get("/")
|> MyApp.RequireAuthentication.call([])
assert conn.halted
@spec bypass_through(Plug.Conn.t(), module()) :: Plug.Conn.t()
调用当前路由的端点和路由器管道。
请参阅 bypass_through/1
。
@spec bypass_through(Plug.Conn.t(), module(), atom() | list()) :: Plug.Conn.t()
调用端点和给定的路由器管道。
请参阅 bypass_through/1
。
@spec clear_flash(Plug.Conn.t()) :: Plug.Conn.t()
清除闪存存储。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
@spec delete_req_cookie(Plug.Conn.t(), binary()) :: Plug.Conn.t()
删除请求 cookie。
将连接分派到给定的端点。
当通过 get/3
、post/3
及其类似函数调用时,端点会自动从 @endpoint
模块属性中检索,否则必须作为参数给出。
连接将使用给定的 method
、path_or_action
和 params_or_body
配置。
如果 path_or_action
是字符串,则将其视为请求路径并存储在连接中。如果是原子值,则将其视为 action,连接将分派到给定的 action。
参数和主体
此函数以及 get/3
、post/3
及其类似函数接受请求主体或参数作为最后一个参数。
get(build_conn(), "/", some: "param")
get(build_conn(), "/", "some=param&url=encoded")
允许的值为:
nil
- 表示没有主体。二进制数据 - 包含请求主体。对于这种情况,
:headers
必须作为选项给出,并带有内容类型。一个映射或列表 - 包含将自动将内容类型设置为 multipart 的参数。该映射或列表可能包含其他列表或映射,所有条目将被规范化为字符串键
一个结构 - 与其他映射不同,结构将按原样传递,不会对其条目进行规范化
@spec ensure_recycled(Plug.Conn.t()) :: Plug.Conn.t()
如果连接尚未循环利用,则确保循环利用连接。
有关详细信息,请参见 recycle/1
。
@spec fetch_flash(Plug.Conn.t()) :: Plug.Conn.t()
获取闪存存储。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
@spec get_flash(Plug.Conn.t()) :: map()
获取整个闪存存储。
@spec get_flash(Plug.Conn.t(), term()) :: term()
从闪存存储中获取给定的键。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
@spec html_response(Plug.Conn.t(), status :: integer() | atom()) :: String.t()
断言给定的状态代码,我们有一个 html 响应,并返回设置或发送的响应正文(如果有)。
示例
assert html_response(conn, 200) =~ "<html>"
@spec init_test_session(Plug.Conn.t(), map() | keyword()) :: Plug.Conn.t()
初始化专门用于测试的会话。
@spec json_response(Plug.Conn.t(), status :: integer() | atom()) :: term()
断言给定的状态代码,我们有一个 json 响应,并返回设置或发送的解码 JSON 响应(如果有)。
示例
body = json_response(conn, 200)
assert "can't be blank" in body["errors"]
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
@spec path_params(Plug.Conn.t(), String.t()) :: map()
返回 %Plug.Conn{}
的路由器对 URL 的匹配参数。
用于从返回的 URL 中提取路径参数,例如 Phoenix.LiveViewTest
的重定向结果返回的 URL。
示例
assert {:error, {:redirect, %{to: "/posts/123" = to}}} = live(conn, "/path")
assert %{id: "123"} = path_params(conn, to)
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。
@spec put_flash(Plug.Conn.t(), term(), term()) :: Plug.Conn.t()
将给定的值放在闪存存储中的 key 下。
@spec put_req_cookie(Plug.Conn.t(), binary(), binary()) :: Plug.Conn.t()
放置请求 cookie。
@spec recycle(Plug.Conn.t(), [String.t()]) :: Plug.Conn.t()
循环利用连接。
回收机制接收一个连接,并返回一个新的连接,其中包含来自给定连接的 cookie 和相关信息。
这模拟了浏览器执行的行为,其中响应中返回的 cookie 在后续请求中可用。
默认情况下,只有“accept”、“accept-language”和“authorization”头会被回收。但是,可以通过将表示其名称的字符串列表作为函数的第二个参数传递来指定自定义头集。
请注意,除非连接已被回收,否则在将请求分派到端点时会自动调用 recycle/1
。
@spec redirected_params(Plug.Conn.t(), status :: non_neg_integer()) :: map()
返回连接重定向到的 URL 的匹配参数。
使用在先前请求中匹配的提供的 %Plug.Conn{}
的路由器。如果响应的 location 头未设置,或者响应与重定向状态代码不匹配(默认为 302),则会引发异常。
示例
assert redirected_to(conn) =~ "/posts/123"
assert %{id: "123"} = redirected_params(conn)
assert %{id: "123"} = redirected_params(conn, 303)
@spec redirected_to(Plug.Conn.t(), status :: non_neg_integer()) :: String.t()
返回给定重定向响应的 Location 标头。
如果响应与重定向状态代码不匹配(默认为 302),则会引发异常。
示例
assert redirected_to(conn) =~ "/foo/bar"
assert redirected_to(conn, 301) =~ "/foo/bar"
assert redirected_to(conn, :moved_permanently) =~ "/foo/bar"
@spec response(Plug.Conn.t(), status :: integer() | atom()) :: binary()
断言给定的状态代码,并返回设置或发送的响应正文(如果有)。
示例
conn = get(build_conn(), "/")
assert response(conn, 200) =~ "hello world"
@spec response_content_type(Plug.Conn.t(), atom()) :: String.t()
返回内容类型(只要它与给定格式匹配)。
示例
# Assert we have an html response with utf-8 charset
assert response_content_type(conn, :html) =~ "charset=utf-8"
@spec text_response(Plug.Conn.t(), status :: integer() | atom()) :: String.t()
断言给定的状态代码,我们有一个文本响应,并返回设置或发送的响应正文(如果有)。
示例
assert text_response(conn, 200) =~ "hello"
分派到当前端点。
请参阅 dispatch/5
以获取更多信息。