I have an in-memory repository that I can create by calling this function:
newEmptyRepository :: IO InMemoryGameRepository
where InMemoryGameRepository
is defined like this:
type State = (HashMap GameId Game)
type IORefState = IORef State
newtype InMemoryGameRepository = InMemoryGameRepository IORefState
When writing tests for my Scotty application I've seen examples of using this approach:
spec =
before app $ do
describe "GET /" $ do
it "responds with 200" $ get "/" `shouldRespondWith` 200
it "responds with 'hello'" $ get "/" `shouldRespondWith` "hello"
...
This is all fine but I need to somehow also initialize the InMemoryGameRepository (by calling newEmptyRepository
) and use the created instance in my tests. Thus I've changed app
to:
app :: InMemoryGameRepository -> IO Application
app repo = scottyApp $ routes repo
And I'm trying to create a test that uses the repository AND the IO Application
, for example like this (which doesn't work):
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
where startGame
is defined like this:
startGame :: InMemoryGameRepository -> IO Game
Here the compiler says (obviously) that repo
is not in scope. But how can I achieve this? I.e. I want to share a single instance of newEmptyRepository
both for the app
and in the test?
Ps: you can see the full application on github.
You should use beforeWith which has the type
beforeWith :: (b -> IO a) -> SpecWith a -> SpecWith b
Use it as e.g. before newEmptyRepository . beforeWith app
whose type is SpecWith Application -> Spec
.
If you want to access both the InMemoryGameRepository
and the Application
in your test cases, defined a helper function
withArg f a = (,) a <$> f a
withArg :: Functor f => (t -> f b) -> t -> f (t, b)
then use
before newEmptyRepository . beforeWith (withArg app)
:: SpecWith (InMemoryGameRepository, Application) -> Spec
Finally, you shouldn't use liftIO $ startGame repo
in the definition of your tests - this runs startGame
every time the test tree is built (although, this may actually be what you want, it doesn't seem to be the case). Instead, if you use the before
family of functions, startGame
will run once before the tests are actually run. You can even access the Game
returned by startGame
using the same technique as above:
before newEmptyRepository
. beforeWith (withArg startGame)
. beforeWith (withArg $ app . fst)
:: SpecWith ((InMemoryGameRepository, Game), Application) -> Spec