如何使用环模拟请求模拟测试 POST 正文为 JSON 的请求?
How to mock test POST requests with body as JSON using ring mock request?
我使用 http-kit 作为服务器,使用 ring.middleware.json
中的 wrap-json-body
来获取从客户端发送的字符串化 JSON 内容作为请求正文。我的 core.clj
是:
; core.clj
; ..
(defroutes app-routes
(POST "/sign" {body :body} (sign body)))
(def app (site #'app-routes))
(defn -main []
(-> app
(wrap-reload)
(wrap-json-body {:keywords? true :bigdecimals? true})
(run-server {:port 8080}))
(println "Server started."))
当我 运行 服务器使用 lein run
时,该方法工作正常。我正在将 JSON 字符串化并从客户端发送。 sign 方法将 json 正确地获取为 {"abc": 1}
。
问题出在模拟测试的时候。 sig
n 方法得到一个 ByteArrayInputStream
并且我正在使用 json/generate-string
转换为在这种情况下失败的字符串。我尝试将处理程序包装在 wrap-json-body
中,但它不起作用。这是我尝试过的测试用例 core_test.clj
:
; core_test.clj
; ..
(deftest create-sign-test
(testing "POST sign"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test1
(testing "POST sign1"
(let [response (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test2
(testing "POST sign2"
(let [response (core/app (-> (mock/body (mock/request :post "/sign")
(json/generate-string {:user 1}))
(mock/content-type "application/json")))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test3
(testing "POST sign3"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" {:headers {"content-type" "application/json"}
:body "{\"foo\": \"bar\"}"}))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
所有失败并出现以下错误:
Uncaught exception, not in assertion.
expected: nil
actual: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.ByteArrayInputStream: java.io.ByteArrayInputStream@4db77402
如何将 JSON 字符串作为正文传递给环模拟测试中的方法?
您的代码中存在三个问题。
您的测试没有将您的应用程序处理程序包装在 wrap-json-body
中,因此它可能无法在您的处理程序中正确解析请求正文。您需要先将 app
包装在 wrap-json-body
中,然后使用您的模拟请求调用它。 (你也可以让你的 app
处理程序已经被包装,而不是将它包装在你的主要功能和测试中)
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})]
(handler your-mock-request))
您的模拟请求不包含正确的内容类型,您的 wrap-json-body
不会将您的请求正文解析为 JSON。这就是为什么您的 sign
函数得到 ByteArrayInputStream
而不是解析 JSON 的原因。您需要将内容类型添加到模拟请求中:
(let [request (-> (mock/request :post "/sign" "{\"username\": \"jane\"}")
(mock/content-type "application/json"))]
(handler request))
验证您的 sign
函数 returns 响应映射,其中 JSON 作为正文中的字符串。如果它创建响应主体作为输入流,您需要在测试函数中解析它。下面我使用 cheshire
来解析它(将 JSON 键转换为关键字):
(cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
此外,您可以使用 Cheshire 将数据编码为 JSON 字符串,而不是手动编写 JSON 请求正文:
(let [json-body (cheshire.core/generate-string {:username "jane"})]
...)
通过这些更改,它应该像我稍作修改的示例一样正常工作:
(defroutes app-routes
(POST "/echo" {body :body}
{:status 200 :body body}))
(def app (site #'app-routes))
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true}))
json-body (json/generate-string {:username "jane"})
request (-> (mock/request :post "/echo" json-body)
(mock/content-type "application/json"))
response (handler request)]
(is (= (:status response) 200))
(is (= (:body response) {:username "jane"})))
我使用 http-kit 作为服务器,使用 ring.middleware.json
中的 wrap-json-body
来获取从客户端发送的字符串化 JSON 内容作为请求正文。我的 core.clj
是:
; core.clj
; ..
(defroutes app-routes
(POST "/sign" {body :body} (sign body)))
(def app (site #'app-routes))
(defn -main []
(-> app
(wrap-reload)
(wrap-json-body {:keywords? true :bigdecimals? true})
(run-server {:port 8080}))
(println "Server started."))
当我 运行 服务器使用 lein run
时,该方法工作正常。我正在将 JSON 字符串化并从客户端发送。 sign 方法将 json 正确地获取为 {"abc": 1}
。
问题出在模拟测试的时候。 sig
n 方法得到一个 ByteArrayInputStream
并且我正在使用 json/generate-string
转换为在这种情况下失败的字符串。我尝试将处理程序包装在 wrap-json-body
中,但它不起作用。这是我尝试过的测试用例 core_test.clj
:
; core_test.clj
; ..
(deftest create-sign-test
(testing "POST sign"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test1
(testing "POST sign1"
(let [response (core/app (mock/request :post "/sign" "{\"username\": \"jane\"}"))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test2
(testing "POST sign2"
(let [response (core/app (-> (mock/body (mock/request :post "/sign")
(json/generate-string {:user 1}))
(mock/content-type "application/json")))]
(is (= (:status response) 200))
(println response))))
(deftest create-sign-test3
(testing "POST sign3"
(let [response
(wrap-json-body (core/app (mock/request :post "/sign" {:headers {"content-type" "application/json"}
:body "{\"foo\": \"bar\"}"}))
{:keywords? true :bigdecimals? true})]
(is (= (:status response) 200))
(println response))))
所有失败并出现以下错误:
Uncaught exception, not in assertion.
expected: nil
actual: com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.ByteArrayInputStream: java.io.ByteArrayInputStream@4db77402
如何将 JSON 字符串作为正文传递给环模拟测试中的方法?
您的代码中存在三个问题。
您的测试没有将您的应用程序处理程序包装在
wrap-json-body
中,因此它可能无法在您的处理程序中正确解析请求正文。您需要先将app
包装在wrap-json-body
中,然后使用您的模拟请求调用它。 (你也可以让你的app
处理程序已经被包装,而不是将它包装在你的主要功能和测试中)(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})] (handler your-mock-request))
您的模拟请求不包含正确的内容类型,您的
wrap-json-body
不会将您的请求正文解析为 JSON。这就是为什么您的sign
函数得到ByteArrayInputStream
而不是解析 JSON 的原因。您需要将内容类型添加到模拟请求中:(let [request (-> (mock/request :post "/sign" "{\"username\": \"jane\"}") (mock/content-type "application/json"))] (handler request))
验证您的
sign
函数 returns 响应映射,其中 JSON 作为正文中的字符串。如果它创建响应主体作为输入流,您需要在测试函数中解析它。下面我使用cheshire
来解析它(将 JSON 键转换为关键字):(cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
此外,您可以使用 Cheshire 将数据编码为 JSON 字符串,而不是手动编写 JSON 请求正文:
(let [json-body (cheshire.core/generate-string {:username "jane"})]
...)
通过这些更改,它应该像我稍作修改的示例一样正常工作:
(defroutes app-routes
(POST "/echo" {body :body}
{:status 200 :body body}))
(def app (site #'app-routes))
(let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true}))
json-body (json/generate-string {:username "jane"})
request (-> (mock/request :post "/echo" json-body)
(mock/content-type "application/json"))
response (handler request)]
(is (= (:status response) 200))
(is (= (:body response) {:username "jane"})))