1

我有一个可以通过调用此函数来创建的内存存储库:

newEmptyRepository :: IO InMemoryGameRepository

whereInMemoryGameRepository定义如下:

type State = (HashMap GameId Game)
type IORefState = IORef State
newtype InMemoryGameRepository = InMemoryGameRepository IORefState

在为我的 Scotty 应用程序编写测试时,我看到了使用这种方法的示例:

spec =
  before app $ do
    describe "GET /" $ do
      it "responds with 200" $ get "/" `shouldRespondWith` 200
      it "responds with 'hello'" $ get "/" `shouldRespondWith` "hello"
    ...

这一切都很好,但我还需要以某种方式初始化 InMemoryGameRepository(通过调用newEmptyRepository)并在我的测试中使用创建的实例。因此,我已更改app为:

app :: InMemoryGameRepository -> IO Application
app repo = scottyApp $ routes repo

我正在尝试创建一个使用存储库和的测试,IO Application例如像这样(不起作用):

spec = 
    before (do repo <- newEmptyRepository
               app repo) $ 
      -- API Tests
      describe "GET /api/games" $ 
        it "responds with " $ do
          liftIO $ startGame repo
          get "/api/games" `shouldRespondWith` singleGameResponse

wherestartGame定义如下:

startGame :: InMemoryGameRepository -> IO Game

在这里,编译器说(显然)repo不在范围内。但我怎样才能做到这一点?即我想在测试中共享 和 的单个newEmptyRepository实例app

Ps:你可以在github上看到完整的应用程序。

4

1 回答 1

2

您应该使用beforeWith具有类型

beforeWith :: (b -> IO a) -> SpecWith a -> SpecWith b

将其用作例如before newEmptyRepository . beforeWith app其类型为SpecWith Application -> Spec.

如果您想在测试用例中同时访问 theInMemoryGameRepository和 the ,请定义一个辅助函数Application

withArg f a = (,) a <$> f a
withArg :: Functor f => (t -> f b) -> t -> f (t, b)

然后使用

before newEmptyRepository . beforeWith (withArg app)
    :: SpecWith (InMemoryGameRepository, Application) -> Spec

最后,您不应该liftIO $ startGame repo在测试的定义中使用 -startGame每次构建测试树时都会运行它(尽管这实际上可能是您想要的,但似乎并非如此)。相反,如果您使用before函数族,startGame将在测试实际运行之前运行一次。您甚至可以使用与上述相同的技术访问Game返回的内容:startGame

  before newEmptyRepository 
. beforeWith (withArg startGame) 
. beforeWith (withArg $ app . fst)
:: SpecWith ((InMemoryGameRepository, Game), Application) -> Spec
于 2017-12-18T16:29:28.570 回答