使用在yesod-tests中返回“Handler Text”的函数

时间:2016-10-17 22:44:39

标签: haskell yesod

这是我的(简化)测试

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

2 个答案:

答案 0 :(得分: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来执行它们,不仅仅是处理程序,还包括测试。< / LI>

    就我而言。如果我想出更多的东西,我打算更新这个答案。我也主演了这个问题,所以如果其他人找到更多或更好的东西,我会收到通知。

    修改: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}}"))
    

答案 1 :(得分: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允许您重用部分服务器代码(路由,数据类型)并管理数据库的状态,但与服务器端操作完全不相关。

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