我有一个内存存储库,可以通过调用此功能来创建:
newEmptyRepository :: IO InMemoryGameRepository
这样定义了InMemoryGameRepository
:
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
这样定义了startGame
:
startGame :: InMemoryGameRepository -> IO Game
在这里,编译器(显然)repo
不在范围内。但是我该如何实现呢?IE。我想在app
和测试中分享newEmptyRepository
的单个实例?
ps:您可以在github上看到完整的应用程序。
您应该在具有类型
的前使用beforeWith :: (b -> IO a) -> SpecWith a -> SpecWith b
使用它为例如before newEmptyRepository . beforeWith app
的类型为SpecWith Application -> Spec
。
如果您想在测试用例中同时访问InMemoryGameRepository
和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
将在实际运行测试之前运行一次。您甚至可以使用与上述相同的技术访问startGame
返回的Game
:
before newEmptyRepository
. beforeWith (withArg startGame)
. beforeWith (withArg $ app . fst)
:: SpecWith ((InMemoryGameRepository, Game), Application) -> Spec