在 yesod-tests 中使用 return "Handler Text" 的函数

Use functions that return "Handler Text" in yesod-tests

这是我的(简化)测试

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

    get $ FlagMentorR flaggedId Flag token
    statusIs 200

我有getFlagTokenFromCsrf :: Text -> 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

我不是 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 允许您重用部分服务器代码(路由、数据类型)并管理数据库的状态,但在其他方面与服务器端操作完全不相关。

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

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

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

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

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

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

  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}}"))