1

我正在尝试使用 Midje 单独测试我的路由。对于一些命中数据库的路由,我可以毫不费力(provided ...)地将路由与真正的数据库调用隔离开来。我已经介绍了 Friend 进行身份验证,但我无法伪造对凭据函数的调用。

我的凭证函数看起来像这样(它是这样实现的,因为我不希望它被调用):

(defn cred-fn
  [creds]
  (println (str "hey look I got called with " creds))
  (throw (Exception.)))

路由的中间件如下所示:

(def app 
  (-> app-routes
      (wrap-json-body {:keywords? true :bigdecimals? true})
      wrap-json-response
      (wrap-defaults defaults)
      (friend/authenticate
       {:unauthorized-handler json-auth/login-failed
        :workflows [(json-auth/json-login
                     :login-uri "/login"
                     :login-failure-handler json-auth/login-failed
                     :credential-fn auth/cred-fn)]})
      (ring-session/wrap-session)))

我也尝试过不使用 auth-json-workflow,路由的实现看起来几乎相同,如果有帮助,我可以添加它,但我得到相同的结果。

然后我的测试看起来像这样(使用 ring-mock):

(defn post [url body]
  (-> (mock/request :post url body)
      (mock/content-type "application/json")
      app))

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/cred-fn anything) => nil))
(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/cred-fn anything) => {:identity "root"}))

然后我得到以下运行测试的输出:

hey look I got called with {:password "admin_password", :username "not-a-user"}
FAIL at (handler.clj:66)
These calls were not made the right number of times:
    (auth/cred-fn anything) [expected at least once, actually never called]
FAIL "routes - authenticated routes - login with incorrect username and password returns unauthenticated" at (handler.clj:64)
    Expected: 401
      Actual: java.lang.Exception
          clojure_api_seed.authentication$cred_fn.invoke(authentication.clj:23)

hey look I got called with {:password "admin_password", :username "root"}
FAIL at (handler.clj:70)
These calls were not made the right number of times:
    (auth/cred-fn anything) [expected at least once, actually never called]

FAIL "routes - authenticated routes - login with correct username and password returns success" at (handler.clj:68)
    Expected: 200
      Actual: java.lang.Exception
          clojure_api_seed.authentication$cred_fn.invoke(authentication.clj:23)

所以从我可以看到提供的声明没有生效,我不知道为什么。有任何想法吗?

4

2 回答 2

1

我最近遇到了类似的问题,经过一番挖掘,我想我明白为什么会发生这种情况。让我们看看绑定如何auth/cred-fn随时间变化。

(clojure.pprint/pprint (macroexpand '(defn cred-fn
                                                [creds]
                                                (println (str "hey look I got called with " creds))
                                                (throw (Exception.)))))
(def
 cred-fn
 (clojure.core/fn
  ([creds]
   (println (str "hey look I got called with " creds))
   (throw (Exception.)))))

正如您在上面看到的,defncred-fn在当前命名空间中实习符号并将其绑定到引用您的虚拟函数的 Var。

(def app 
    (-> app-routes
        (wrap-json-body {:keywords? true :bigdecimals? true})
        wrap-json-response
        (wrap-defaults defaults)
        (friend/authenticate
        {:unauthorized-handler json-auth/login-failed
        :workflows [(json-auth/json-login
                    :login-uri "/login"
                    :login-failure-handler json-auth/login-failed
                    :credential-fn auth/cred-fn)]})
        (ring-session/wrap-session)))

这是重要的部分。在编译时,我们线程 app-routes通过一系列函数。其中一个功能是friend/authenticate,它采用带有 key 的地图:workflows。的值:workflows是一个向量,其中填充了对 的调用结果,该调用 作为参数json-auth/json-login接收。auth/credential-fn请记住,我们在一个 def 中,所以这一切都发生在编译时。我们在 auth 命名空间中查找符号cred-fn,并传入符号绑定的 Var。在这一点上,这仍然是虚拟实现。大概,json-auth/json-login捕获此实现并设置调用它的请求处理程序。

(fact "login with incorrect username and password returns unauthenticated"
    (:status (post "/login" invalid-auth-account-json)) => 401
    (provided
    (auth/cred-fn anything) => nil))

现在我们在运行时。在我们的前提条件中,Midje 将符号重新绑定auth/cred-fn到引用模拟的 Var。但是当我们在编译时'd 的值auth/cred-fn已经被捕获。defapp

那么您发布的解决方法是如何起作用的呢?(这实际上是引导我进入尤里卡时刻的线索——谢谢。)

(defn another-fn []
  (println (str "hey look I got called"))
  (throw (Exception.)))

(defn cred-fn [creds]
  (another-fn))

在你的测试中......

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/another-fn) => nil))

(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/another-fn) => {:identity "root"}))

这是有效的,因为在编译时,auth/cred-fn被捕获的值是一个简单地委托给 auth/another-fn. 请注意,auth/another-fn尚未评估。现在在我们的测试中,Midje 重新绑定auth/another-fn 以引用模拟。然后它执行 post,并在中间件的某个地方auth/cred-fn被调用。在内部 auth/cred-fn,我们查找绑定到的 Var auth/another-fn(这是我们的模拟),并调用它。当然,现在的行为与您第一次预期的完全一样。

这个故事的寓意是,小心 Clojure 中的 def

于 2015-05-02T23:10:46.363 回答
0

我仍然不确定为什么会发生这种情况,但我有一个解决方法。如果我将我的替换credential-fn为:

(defn another-fn
  []
  (println (str "hey look I got called"))
  (throw (Exception.)))

(defn cred-fn
  [creds]
  (another-fn))

然后为测试中的新函数创建一个假,如下所示:

(fact "login with incorrect username and password returns unauthenticated"
  (:status (post "/login" invalid-auth-account-json)) => 401
  (provided
    (auth/another-fn) => nil))

(fact "login with correct username and password returns success"
  (:status (post "/login" auth-account-json)) => 200
  (provided
    (auth/another-fn) => {:identity "root"}))

我得到了我期待的结果。cred-fn仍然被调用,但another-fn由于provided.

如果有人知道为什么会这样,我会很想知道。这可能是由于凭证函数被调用的方式?- https://github.com/marianoguerra/friend-json-workflow/blob/master/src/marianoguerra/friend_json_workflow.clj#L46

于 2014-11-14T15:05:18.813 回答