【问题标题】:Use functions that return "Handler Text" in yesod-tests在 yesod-tests 中使用返回“处理程序文本”的函数
【发布时间】:2017-02-27 01:16:25
【问题描述】:

这是我的(简化的)测试

it "asserts route access for valid arguments" $ do
    -- ... 
    token <- getFlagTokenFromCsrf "1234"

    get $ FlagMentorR flaggedId Flag token
    statusIs 200

我在哪里getFlagTokenFromCsrf :: Text -&gt; Handler Text

我尝试stack test 时遇到的错误是:

Couldn't match type ‘Yesod.Core.Types.HandlerT App IO Text’
               with ‘Control.Monad.Trans.State.Lazy.StateT
                       (YesodExampleData App) IO Text’
Expected type: Control.Monad.Trans.State.Lazy.StateT
                 (YesodExampleData App) IO Text
  Actual type: Handler Text

(这里是full example

【问题讨论】:

    标签: haskell yesod


    【解决方案1】:

    我不是 Yesod 专家,但您尝试做的事情可能没有意义:getFlagTokenFromCsrf 是一个从请求中提取一些信息的服务器端函数,因此它存在于 Handler a monad 中,因为这是服务器端代码的 monad。

    相比之下,Yesod 测试是集成测试,从客户端针对您的Application 断言行为(请参阅https://hackage.haskell.org/package/yesod-test-1.5.3/docs/Yesod-Test.html)。那里的函数存在于不同的 monad 中,即 YesodExample site a 代表您管理请求。此 monad 允许您重用部分服务器代码(路由、数据类型)并管理 DB 的状态,但与服务器端操作完全不相关。

    我不确定您要做什么,但如果您需要在服务器端生成一些信息,您必须找到另一种方法使其在客户端可用。

    【讨论】:

    • 我明白了。我认为也可以进行单元测试。
    • 是的。我认为在这种特殊情况下进行单元测试会很有意义。或者找到一种在客户端注入已知令牌的方法,以便您可以从服务器的响应中检查它是否确实检查了令牌
    【解决方案2】:

    看来我正在尝试做一些类似的事情。即:尝试在单元测试中运行任意Handler a 函数。

    我尝试并考虑过的一些方法:

    1. 我得到的最接近实际对我有用的:

      import Application (handler)
      
      spec = withApp $ do
        it "" $ do
          liftIO $ handler $ someHandler
      

      尽管it seems this calls makeFoundation under the hood,但我希望我的处理程序能够在主 web 应用程序的上下文中运行,而不是在单独的实例中运行。

    2. 为了在主应用的上下文中执行处理程序,我也尝试过:

      import Foundation (unsafeHandler)
      import qualified Control.Monad.Trans.State as ST
      
      spec = withApp $ do
          it "" $ do
            (YesodExampleData app _ _ _) <- ST.get
            liftIO $ unsafeHandler app $ someHandler
      

      这让我遇到了这样的类型错误:

      [34 of 35] Compiling Handler.FooSpec ( /path/to/test/Handler/FooSpec.hs, /path/to/.stack-work/odir/Handler/FooSpec.o )
      
      /path/to/test/Handler/FooSpec.hs:42:31:
          Couldn't match type ‘Network.Wai.Internal.Request
                               -> (Network.Wai.Internal.Response
                                   -> IO Network.Wai.Internal.ResponseReceived)
                               -> IO Network.Wai.Internal.ResponseReceived’
                         with ‘App’
          Expected type: App
            Actual type: Network.Wai.Application
          Probable cause: ‘app’ is applied to too few arguments
          In the first argument of ‘unsafeHandler’, namely ‘app’
          In the expression: unsafeHandler app
      Failed, modules loaded: Import, Utils, Foundation, [...]
      

      我并不完全清楚 Network.Wai.ApplicationApp 之间的区别,但这可能是完成这项工作的关键。

    3. 我还考虑过指定到处理程序的路由,然后可以通过get SomeRouteR 访问它。虽然如果我能帮上忙,我宁愿不必这样做。

    4. 我还在考虑将我的一些Handler a 函数重构为IO a,然后我可以使用liftIO 不仅在处理程序中执行它们,还可以在测试中执行它们。

    就我所知。如果我想出更多东西,我打算更新这个答案。我还为这个问题加注了星标,因此如果其他人发现更多或更好的东西,我会收到通知。

    编辑:I've also asked this question on GitHub

    编辑:

    在尝试朝着选项 #2 的方向进一步挖掘时,我发现了一些很有希望的东西:

    import Foundation (unsafeHandler)
    
    runHandler :: Handler a -> ST.StateT (YesodExampleData App) IO a
    runHandler handler = do
      foundation <- getTestYesod
      liftIO $ unsafeHandler foundation handler
    

    我能够像这样运行测试:

    it "runHandler" $ do
      let
        testHandler :: Handler Int
        testHandler = do
          return 2
      runHandler testHandler >>= (==? 2)
    
      let
        testHandler2 :: Handler String
        testHandler2 = do
          fmap show $ (Import.runDB) $ insert (def :: User)
      runHandler testHandler2 >>= (==? ("UserKey {unUserKey = SqlBackendKey {unSqlBackendKey = 1}}"))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-09
      • 2015-09-25
      • 1970-01-01
      • 1970-01-01
      • 2014-03-17
      • 2012-07-03
      相关资源
      最近更新 更多