Как имитировать тестовые POST-запросы с телом в виде JSON, используя кольцевой макет запроса?

#unit-testing #clojure #mocking #ring #http-kit

#модульное тестирование #clojure #издевательство #ring #http-kit

Вопрос:

Я использую http-kit в качестве сервера с wrap-json-body помощью from ring.middleware.json , чтобы получить строковое содержимое 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 получает a 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 в качестве тела методу в ring mock test?

Ответ №1:

В вашем коде есть три проблемы.

  1. Ваш тест не включает обработчик вашего приложения wrap-json-body , поэтому он может неправильно проанализировать тело запроса в вашем обработчике. Вам нужно сначала обернуть свой app in wrap-json-body , а затем вызвать его с вашим макетным запросом. (Вы также могли app бы уже обернуть свой обработчик вместо того, чтобы оборачивать его как в вашей основной функции, так и в тестах)

     (let [handler (-> app (wrap-json-body {:keywords? true :bigdecimals? true})]
      (handler your-mock-request))
      
  2. Ваш макет запроса не включает правильный тип содержимого, и вы wrap-json-body не будете анализировать тело вашего запроса в JSON. Вот почему ваша sign функция получает ByteArrayInputStream вместо проанализированного JSON. Вам нужно добавить тип содержимого в свой макет запроса:

     (let [request (-> (mock/request :post "/sign" "{"username": "jane"}")
                      (mock/content-type "application/json"))]
      (handler request))
      
  3. Убедитесь, что ваша sign функция возвращает карту ответов с JSON в виде строки в теле. Если он создает тело ответа в качестве входного потока, вам необходимо проанализировать его в вашей тестовой функции. Ниже я использую cheshire его для анализа (преобразование ключей JSON в ключевые слова):

     (cheshire.core/parse-stream (-> response :body clojure.java.io/reader) keyword)
      

Кроме того, вместо написания тела запроса JSON вручную вы можете использовать Cheshire для кодирования ваших данных в строку 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"})))
  

Комментарии:

1. Большое вам спасибо! sign Метод правильно получает JSON. Я часами пытался сделать это правильно.